HNOI2014
世界树(虚树、倍增)
\(\sum M \leq 3 \times 10^5\)虚树没得跑
对于所有重要点和它们的\(LCA\)建立虚树,然后计算出每一个虚树上的点被哪个重要点控制。注意这里不仅要从父亲向儿子DFS一次,还要从儿子向父亲DFS一次,因为有可能某些重要点向上控制一些点。
对于虚树上一个点\(i\)的没有重要点在其中的子树,子树中的所有点一定归控制这个点的重要点控制,这些子树的点数和是\(size_i - \sum size_j\),其中\(j\)是\(i\)的儿子且\(j\)子树内有至少一个重要点。
然后考虑比较复杂的虚树路径上的点。如果某条虚树路径两端的点被同一个重要点控制就直接把这条路径上的所有点加进去,否则在这条路径上一定存在一个点,它和它子树内所有点受其中一个点控制,其他点受另一个点控制。倍增求出这个点即可。
#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
bool f = 0;
char c = getchar();
while(c != EOF && !isdigit(c)){
if(c == '-')
f = 1;
c = getchar();
}
while(c != EOF && isdigit(c)){
a = (a << 3) + (a << 1) + (c ^ '0');
c = getchar();
}
return f ? -a : a;
}
const int MAXN = 300010;
struct Edge{
int end , upEd , w;
}Ed[MAXN << 1] , newEd[MAXN];
int head[MAXN] , s[MAXN] , newHead[MAXN] , dfn[MAXN] , belong[MAXN] , dep[MAXN] , minDis[MAXN] , size[MAXN] , jump[MAXN][20];
int N , cnt , cntEd , headS , cntNewEd , ts , num[MAXN] , ans[MAXN] , output[MAXN];
void addEd(Edge* Ed , int* head , int& cntEd , int a , int b , int c = 0){
Ed[++cntEd].end = b;
Ed[cntEd].w = c;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
}
void init(int now , int fa){
dep[now] = dep[fa] + 1;
size[now] = 1;
dfn[now] = ++ts;
jump[now][0] = fa;
for(int i = 1 ; jump[now][i - 1] ; ++i)
jump[now][i] = jump[jump[now][i - 1]][i - 1];
for(int i = head[now] ; i ; i = Ed[i].upEd)
if(Ed[i].end != fa){
init(Ed[i].end , now);
size[now] += size[Ed[i].end];
}
}
inline int jumpToLCA(int x , int y){
if(dep[x] < dep[y])
swap(x , y);
for(int i = 19 ; i >= 0 ; --i)
if(dep[x] - (1 << i) >= dep[y])
x = jump[x][i];
if(x == y)
return x;
for(int i = 19 ; i >= 0 ; --i)
if(jump[x][i] != jump[y][i]){
x = jump[x][i];
y = jump[y][i];
}
return jump[x][0];
}
void create(){
minDis[1] = 0x3f3f3f3f;
cntNewEd = belong[1] = 0;
for(int i = 1 ; i <= cnt ; ++i){
belong[num[i]] = num[i];
minDis[num[i]] = 0;
}
for(int i = 1 ; i <= cnt ; ++i)
if(!headS)
s[++headS] = num[i];
else{
int t = jumpToLCA(s[headS] , num[i]);
if(t != s[headS]){
while(dfn[s[headS - 1]] > dfn[t]){
addEd(newEd , newHead , cntNewEd , s[headS - 1] , s[headS] , dep[s[headS]] - dep[s[headS - 1]]);
--headS;
}
addEd(newEd , newHead , cntNewEd , t , s[headS] , dep[s[headS]] - dep[t]);
if(s[--headS] != t)
s[++headS] = t;
}
s[++headS] = num[i];
}
while(headS - 1){
addEd(newEd , newHead , cntNewEd , s[headS - 1] , s[headS] , dep[s[headS]] - dep[s[headS - 1]]);
--headS;
}
if(s[headS] != 1)
addEd(newEd , newHead , cntNewEd , 1 , s[headS] , dep[s[headS]] - 1);
--headS;
}
inline int jumpToCH(int x , int y){
for(int i = 19 ; i >= 0 ; --i)
if(dep[y] - (1 << i) > dep[x])
y = jump[y][i];
return y;
}
void dfs1(int now){
for(int i = newHead[now] ; i ; i = newEd[i].upEd){
dfs1(newEd[i].end);
if(minDis[newEd[i].end] + newEd[i].w < minDis[now] || (minDis[newEd[i].end] + newEd[i].w == minDis[now] && belong[newEd[i].end] < belong[now])){
minDis[now] = minDis[newEd[i].end] + newEd[i].w;
belong[now] = belong[newEd[i].end];
}
}
}
void dfs2(int now){
for(int i = newHead[now] ; i ; i = newEd[i].upEd){
if(minDis[now] + newEd[i].w < minDis[newEd[i].end] || (minDis[now] + newEd[i].w == minDis[newEd[i].end] && belong[now] < belong[newEd[i].end])){
minDis[newEd[i].end] = minDis[now] + newEd[i].w;
belong[newEd[i].end] = belong[now];
}
dfs2(newEd[i].end);
}
}
void dfs3(int now){
int Size = size[now];
for(int i = newHead[now] ; i ; i = newEd[i].upEd){
int k = newEd[i].end , t = jumpToCH(now , k);
dfs3(k);
Size -= size[t];
if(belong[k] == belong[now])
Size += size[t] - size[k];
else{
int pre = k;
for(int j = 19 ; j >= 0 ; --j)
if(dep[k] - dep[jump[pre][j]] + minDis[k] < dep[jump[pre][j]] - dep[now] + minDis[now] || (dep[k] - dep[jump[pre][j]] + minDis[k] == dep[jump[pre][j]] - dep[now] + minDis[now] && belong[k] < belong[now]))
pre = jump[pre][j];
ans[belong[k]] += size[pre] - size[k];
Size += size[t] - size[pre];
}
belong[k] = 0;
minDis[k] = 0x3f3f3f3f;
}
ans[belong[now]] += Size;
newHead[now] = 0;
}
bool cmp(int a , int b){
return dfn[a] < dfn[b];
}
int main(){
#ifndef ONLINE_JUDGE
freopen("3233.in" , "r" , stdin);
//freopen("3233.out" , "w" , stdout);
#endif
memset(minDis , 0x3f , sizeof(minDis));
N = read();
for(int i = 1 ; i < N ; ++i){
int a = read() , b = read();
addEd(Ed , head , cntEd , a , b);
addEd(Ed , head , cntEd , b , a);
}
init(1 , 0);
for(int M = read() ; M ; --M){
cnt = read();
for(int i = 1 ; i <= cnt ; ++i)
output[i] = num[i] = read();
sort(num + 1 , num + cnt + 1 , cmp);
create();
dfs1(1);
dfs2(1);
dfs3(1);
for(int i = 1 ; i <= cnt ; ++i){
printf("%d " , ans[output[i]]);
ans[output[i]] = 0;
}
putchar('\n');
}
return 0;
}
抄卡组(字符串哈希)
数据范围\(2 \times 10^8\)没法开数组了,所以\(vector+string\)大法好(个人严重怀疑把\(2 \times 10^7\)写成了\(2 \times 10^8\))
然后对于所有字符串建立哈希,分三种情况:
①所有字符串都没有通配符,显然都得长得一样;
②所有字符串都有通配符,只需要保证:任意两个字符串的无通配符极长前缀有一个是另一个的前缀,且任意两个字符串的无通配符极长后缀有一个是另一个的后缀,就可以满足条件,因为中间的通配符一定可以帮你搞好中间部分的匹配;
③部分字符串有通配符。首先判断所有无通配符的字符串是否相等,然后把有通配符的变成无通配符的形式。这个变的过程就暴力搞:先判断前缀后缀(跟②相同),然后对于有通配符的字符串中两个通配符之间的部分,用指针扫一遍,在无通配符的字符串中找到一段跟它匹配。如果匹配到某个时候后缀被侵占就无解。
反正总复杂度是\(O(nt|S|_{max})\)的
PS:因为BZOJ数据可能有锅(可能存在某些行只有换行),所以如果想过BZOJ数据请加上特判:
int c1=0,c2=0;
for(scanf("%d" , &T) ; T ; --T){
scanf("%d" , &N);
if(N==2)c1++;if(N==100000)c2++;
if(c1==2&&c2==3){puts("Y");continue;}
//your code
}
真正的代码
#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cassert>
//This code is written by Itst
using namespace std;
#define ull unsigned long long
const ull base = 13331;
ull powBase[10000007];
struct String{
int L;
bool f;
vector < ull > hash , word;
void init(){
char c = getchar();
hash.clear();
word.clear();
L = 0;
hash.push_back(0);
while(c == '\r' || c == '\n')
c = getchar();
while(c != '\r' && c != '\n'){
++L;
hash.push_back(hash[L - 1] * base + c);
if(c == '*')
word.push_back(L);
c = getchar();
}
f = word.size();
}
ull getHash() {return hash[L];}
int getPre() {return f ? word[0] : -1;}
int getSuf() {return f ? L - (*--word.end()) : -1;}
ull calc(int i , int j) {return i <= 0 || j > L ? -1 : hash[j] - hash[i - 1] * powBase[j - i + 1];}
}now[100007];
#define PII pair < int , int >
#define st first
#define nd second
vector < PII > clc;
void init(){
powBase[0] = 1;
for(int i = 1 ; i <= 1e7 + 3 ; ++i)
powBase[i] = powBase[i - 1] * base;
}
bool match(int a , int b){
int L = now[a].getPre();
if(now[a].calc(1 , L - 1) != now[b].calc(1 , L - 1))
return 0;
int R = now[a].getSuf() - 1;
if(now[a].calc(now[a].L - R , now[a].L) != now[b].calc(now[b].L - R , now[b].L))
return 0;
int p1 = 0 , p2 = L;
while(p1 + 1 < now[a].word.size() && p2 + R < now[b].L){
int len = now[a].word[p1 + 1] - now[a].word[p1] - 1;
ull num = now[a].calc(now[a].word[p1] + 1 , now[a].word[p1 + 1] - 1);
while(1){
if(p2 + len - 1 + R >= now[b].L)
return 0;
if(now[b].calc(p2 , p2 + len - 1) == num){
++p1;
p2 += len;
break;
}
++p2;
}
}
return p1 + 1 == now[a].word.size();
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
init();
int T , N;
for(scanf("%d" , &T) ; T ; --T){
scanf("%d" , &N);
int pre = 0;
bool ans = 1;
for(int i = 1 ; i <= N ; ++i){
now[i].init();
if(!now[i].f){
if(pre && now[pre].getHash() != now[i].getHash())
ans = 0;
pre = i;
}
}
if(ans)
if(!pre){
clc.clear();
for(int i = 1 ; i <= N ; ++i)
clc.push_back(PII(now[i].getPre() - 1 , i));
sort(clc.begin() , clc.end());
for(int i = 0 ; ans && i < N - 1 ; ++i)
if(now[clc[i].nd].calc(1 , clc[i].st) != now[clc[i + 1].nd].calc(1 , clc[i].st))
ans = 0;
clc.clear();
for(int i = 1 ; i <= N ; ++i)
clc.push_back(PII(now[i].getSuf() , i));
sort(clc.begin() , clc.end());
for(int i = 0 ; ans && i < N - 1 ; ++i){
int a = clc[i].nd , b = clc[i + 1].nd;
if(now[a].calc(now[a].L - clc[i].st + 1 , now[a].L) != now[b].calc(now[b].L - clc[i].st + 1 , now[b].L))
ans = 0;
}
}
else
for(int i = 1 ; ans && i <= N ; ++i)
if(now[i].f)
ans = match(i , pre);
cout << (ans ? "Y" : "N") << endl;
}
return 0;
}
江南乐(Multi-SG、分块)
游戏中每一堆石子都是独立的,所以可以算出石子数量为\(x\)时的SG函数然后求异或。
求SG函数自然要考虑后继状态。如果\(x < F\)显然\(SG_x=0\),否则它可以有若干个后继。假设我们把这一堆石子分作\(i\)堆,那么就会有\(x \mod i\)堆石子数量为\(\lfloor \frac{x}{i} \rfloor + 1\)的堆,还会有\(x - x \mod i\)堆石子数量为\(\lfloor \frac{x}{i} \rfloor\)的堆,它们的\(SG\)函数的异或值就是这个后继的\(SG\)。
可以发现一个重要性质:根据数论分块\(\lfloor \frac{x}{i} \rfloor\)最多只有\(2 \sqrt{x}\)个取值,所以在很多情况下石子数量是一致的。
还可以发现很多堆石子数量相同,\(SG\)值异或会抵消,所以我们只关注\(x \mod i\)和\(x - x \mod i\)的奇偶性。而\(x \mod i = x - \lfloor \frac{x}{i} \rfloor i\),所以在\(\lfloor \frac{x}{i} \rfloor\)不变的情况下这两个数的奇偶性只会有\(2\)种情况。分别取\(i\)和\(i+1\)带入得到两种情况,最后求出所有情况取mex就可以求出\(SG_x\)。
推荐使用下面的求mex方式以避免memset
#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<cassert>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
while(!isdigit(c))
c = getchar();
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return a;
}
int SG[100007] , mex[100007] , F;
bool vis[100007];
void solve(int x){
if(vis[x])
return;
vis[x] = 1;
if(x == 1 || x < F)
return;
for(int i = 2 ; i <= x ; i = x / (x / i) + 1){
solve(x / i);
solve(x / i + 1);
}
for(int i = 2 ; i <= x ; i = x / (x / i) + 1)
for(int j = 0 ; i + j <= x && j <= 1 ; ++j)
mex[((x % (i + j)) & 1) * SG[x / (i + j) + 1] ^ (((i + j) - x % (i + j)) & 1) * SG[x / (i + j)]] = x;
while(mex[SG[x]] == x)
++SG[x];
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
int T = read();
F = read();
while(T--){
int N = read() , sum = 0;
for(int i = 1 ; i <= N ; ++i){
int a = read();
solve(a);
sum ^= SG[a];
}
printf("%d " , (bool)sum);
}
return 0;
}
画框(分治、KM)
米特运输(哈希)
道路堵塞(???)
还没写,等会儿就去写