这次题目比较简单。
The Tom and Jerry Game!
略
Operations on a Tuple
略
The Delicious Cake
略
Convenient Airports
注意到答案的下界为2?max?(N?M?1,?d02?)2\cdot \max(N-M-1,\lceil \frac{d_0}{2}\rceil)2?max(N?M?1,?2d0???),其中d0d_0d0?为度数为000的点个数,下面给出一个能达到这个下界的构造。
对于两个内部有边的连通块,我们在第一个连通块中取出一条边(u1,v1)(u_1,v_1)(u1?,v1?),第二个连通块中取出一条边(u2,v2)(u_2,v_2)(u2?,v2?)。若(u1,v1)(u_1,v_1)(u1?,v1?)和(u2,v2)(u_2,v_2)(u2?,v2?)中至少有一条边不为对应连通块的割边,我们可以进行一次操作:删去(u1,v1)(u_1,v_1)(u1?,v1?)和(u2,v2)(u_2,v_2)(u2?,v2?),加入(u1,v2)(u_1,v_2)(u1?,v2?)和(u2,v1)(u_2,v_1)(u2?,v1?)。这样不会改变任何点的度数,且能合并两个连通块。
那么可以描述我们的算法了:一开始有一个空连通块SSS,我们先依次用上面的操作合并SSS与所有有环的连通块(显然一定能做到),然后考虑合并SSS与所有无环但非单点的连通块,如果SSS此时有非割边,可以继续操作,否则整个图已经是森林,需要多加一条边。最后考虑合并SSS与所有度数为000的单点,如果SSS此时有非割边(u1,v1)(u_1,v_1)(u1?,v1?)且至少有两个这样的单点u2u_2u2?和v2v_2v2?,可以删去(u1,v1)(u_1,v_1)(u1?,v1?),加入(u1,v2)(u_1,v_2)(u1?,v2?)与(u2,v1)(u_2,v_1)(u2?,v1?),这样只需加入一条边就可以合并两个单点,否则只能每次加入一条边合并一个单点。容易证明这个算法可以达到上面给出的下界。
实现的时候,可以考虑用队列储存当前SSS中所有不在DFS树上的边,这些边显然是非割边,可以任意删去,每次合并一个连通块DFS一下即可。
单组数据时间复杂度为O(N+M)\mathcal O(N+M)O(N+M)。
#include <bits/stdc++.h>
#define last last2using namespace std;struct Edge {
int t,next;Edge() {
}Edge(int a,int b):t(a),next(b) {
}
};Edge e[1000005];
int head[100005],q[1000005],l,r,tot;
bool vis1[100005],vis2[1000005],vis3[100005];inline void addEdge(int x,int y) {
e[++tot]=Edge(y,head[x]);head[x]=tot;e[++tot]=Edge(x,head[y]);head[y]=tot;
}int id[100005],s1,s2;
bool kind[100005];void dfs1(int x) {
s1++;for(int i=head[x];i;i=e[i].next) {
s2++;int u=e[i].t;if (!id[u]) {
id[u]=id[x];dfs1(u);}}
}int dep[100005];void dfs2(int x) {
vis1[x]=1;for(int i=head[x];i;i=e[i].next)if (id[e[i].t]==id[x]&&!vis2[(i+1)>>1]) {
int u=e[i].t;if (!vis1[u]) {
dep[u]=dep[x]+1;dfs2(u);}else if (dep[u]>dep[x]) q[++r]=((i+1)>>1);}
}int main() {
int cases;scanf("%d",&cases);for(;cases;cases--) {
memset(head,0,sizeof(head));memset(vis1,0,sizeof(vis1));memset(vis2,0,sizeof(vis2));memset(vis3,0,sizeof(vis3));memset(id,0,sizeof(id));int n,m;scanf("%d%d",&n,&m);tot=0;for(int i=1;i<=m;i++) {
int x,y;scanf("%d%d",&x,&y);addEdge(x,y);}int s0=0,cnt=0;for(int i=1;i<=n;i++)if (!id[i]) {
if (!head[i]) s0++;id[i]=++cnt;s1=s2=0;dfs1(i);kind[cnt]=(s1<=(s2>>1));}printf("%d ",max(2*(n-1-m),2*((s0+1)>>1)));l=1;r=0;int rt=0;for(int i=1;i<=n;i++)if (!vis3[id[i]]&&kind[id[i]]) {
vis3[id[i]]=1;if (l<=r) {
int t1=q[l++],t2=((head[i]+1)>>1);vis2[t1]=vis2[t2]=1;int u1=e[2*t1-1].t,v1=e[2*t1].t;int u2=e[2*t2-1].t,v2=e[2*t2].t;addEdge(u1,u2);addEdge(v1,v2);dep[u2]=0;dfs2(u2);if (vis1[v2]) q[++r]=(tot>>1);else {
dep[v2]=0;dfs2(v2);}}else {
dep[i]=0;dfs2(i);rt=i;}}for(int i=1;i<=n;i++)if (!vis3[id[i]]&&head[i]) {
vis3[id[i]]=1;if (l<=r) {
int t1=q[l++],t2=((head[i]+1)>>1);vis2[t1]=vis2[t2]=1;int u1=e[2*t1-1].t,v1=e[2*t1].t;int u2=e[2*t2-1].t,v2=e[2*t2].t;addEdge(u1,u2);addEdge(v1,v2);}else if (rt) addEdge(rt,i);else rt=i;}int last=0;for(int i=1;i<=n;i++)if (!vis3[id[i]]&&!head[i]) {
vis3[id[i]]=1;if (l<=r) {
if (last) {
int t=q[l++];vis2[t]=1;int u=e[2*t-1].t,v=e[2*t].t;addEdge(u,last);addEdge(v,i);last=0;}else last=i;}else addEdge(rt,i);}if (last) addEdge(rt,last);int s=0;for(int i=1;i<=(tot>>1);i++)if (!vis2[i]) s++;printf("%d\n",s);for(int i=1;i<=(tot>>1);i++)if (!vis2[i]) printf("%d %d\n",e[2*i].t,e[2*i-1].t);}return 0;
}
Guessing Game
可能是这次月赛最难的题目。得出O(log?N)\mathcal O(\log N)O(logN)次询问的算法不难,但要通过K=120K=120K=120的限制,需要进一步的分析和讨论。
先考虑一个暴力一些的算法。维护一个当前答案可能的集合,当集合大小为常数时暴力询问,且任意时刻若返回′E′'E'′E′可以直接终止,否则每次找到集合12\frac{1}{2}21?位置的数aaa,询问aaa。显然这里返回′G′'G'′G′和′L′'L'′L′是等价的,不妨假设返回′G′'G'′G′。再询问集合14\frac{1}{4}41?位置的数bbb,此时再返回′G′'G'′G′意味着两次询问中至少有一个返回信息是真的,那么显然≤b\leq b≤b的数不可能是SSS,可以删去,返回‘L′‘L'‘L′的话意味着两次信息是冲突的,而两次询问中至少有一个返回信息是真的,那么b?ab\sim ab?a之间的数必然不可能是SSS。无论哪一种情况至少能让集合大小减小14\frac{1}{4}41?,大约需要2?log?43n+O(1)≈1442\cdot \log_{\frac{4}{3}}n+\mathcal O(1)\approx 1442?log34??n+O(1)≈144次,不能通过。
优化一下这个算法。注意到如果上面第二种情况下,我们其实没有完全地利用信息,此时如果我们接着在≥a\geq a≥a的部分询问一次,由于上次是询问bbb返回了′L′'L'′L′,因此不论这次返回什么都一定能进一步删除。考虑改变bbb为集合13\frac{1}{3}31?位置的数,这样如果返回′G′'G'′G′可以减小集合大小至少13\frac{1}{3}31?。否则再询问集合34\frac{3}{4}43?位置的数ccc,返回′G′'G'′G′的话可以删去b?cb\sim cb?c之间的数,返回′L′'L'′L′的话则可以删去b?ab\sim ab?a与≥c\geq c≥c的数,不论哪种情况都可以减小集合大小至少512\frac{5}{12}125?。这样每轮要么用222次操作减小13\frac{1}{3}31?,要么用333次操作减小512\frac{5}{12}125?,大约需要max?(2?log?32n,3?log?127n)+O(1)≈115\max(2\cdot \log_{\frac{3}{2}}n,3\cdot \log_{\frac{12}{7}}n)+\mathcal O(1)\approx 115max(2?log23??n,3?log712??n)+O(1)≈115次,可以通过。也可以把13\frac{1}{3}31?用二分改进为更精确的常数,不过优化不大。
实现的时候需要维护答案可能的集合,它任意时刻是O(log?N)\mathcal O(\log N)O(logN)段连续区间的并,暴力维护的话时间复杂度为O(log?2N)\mathcal O(\log^2N)O(log2N)。
#include <bits/stdc++.h>
#define FR first
#define SE secondusing namespace std;typedef long long ll;
typedef pair<int,int> pr;int ans,k;bool ask(int x) {
printf("%d\n",x);fflush(stdout);char str[5];scanf("%s",str);if (str[0]=='E') exit(0);return str[0]=='G';/*k++;if (x==ans) {printf("%d %d\n",ans,k);exit(0);}if (k&1) return ans>x;else return (ans>x)^(rand()%2);*/
}pr a[1005];
int sz;int erase(int l,int r) {
static pr cur[1005];int nsz=0;for(int i=1;i<=sz;i++)if (a[i].FR>r||a[i].SE<l) cur[++nsz]=a[i];else {
if (a[i].FR<l) cur[++nsz]=pr(a[i].FR,l-1);if (a[i].SE>r) cur[++nsz]=pr(r+1,a[i].SE);}sz=nsz;int s=0;for(int i=1;i<=sz;i++) {
a[i]=cur[i];s+=a[i].SE-a[i].FR+1;}return s;
}int query(int k) {
for(int i=1;i<=sz;i++) {
int v=a[i].SE-a[i].FR+1;if (k>v) k-=v;else return a[i].FR+k-1;}
}int randint() {
return ((ll)rand()*9116111716LL+rand())%1000000007;
}int main() {
int n;scanf("%d",&n);//ans=randint()%n+1;sz=1;a[1]=pr(1,n);while (n>3) {
int mid=query((n+1)>>1);if (!ask(mid)) {
int r=query(n-n/3+1);if (!ask(r)) n=erase(r,1e9);else {
int l=query(n/4);if (!ask(l)) n=erase(l,r);else {
n=erase(1,l);n=erase(mid,r);}}}else {
int l=query(n/3);if (ask(l)) n=erase(1,l);else {
int r=query(n-n/4+1);if (ask(r)) n=erase(l,r);else {
n=erase(r,1e9);n=erase(l,mid);}}}}for(int i=1;i<=sz;i++)for(int j=a[i].FR;j<=a[i].SE;j++) ask(j);return 0;
}
Danya and Different Values
对每种颜色ccc分开考虑。对于每个点xxx,我们显然只需要知道ccc在xxx子树中的最小深度(不存在为inf?\infinf)。那么对于颜色ccc的点构成的虚树,虚树上每条链上的点对应的子树中最小深度相同(不在这些链上的点子树中最小深度为inf?\infinf,不用考虑)。那么建出虚树,作一个简单的差分可以将所有颜色对应的修改拆成O(n)\mathcal O(n)O(n)组(u,v,w)(u,v,w)(u,v,w)的修改,意思是对XXX为uuu到根的路径上的点,且depx+D≥vdep_x+D\geq vdepx?+D≥v的询问会有www的贡献。
那么对深度建线段树,直接对子树线段树合并即可。单组数据时间复杂度为O((N+Q)log?N)\mathcal O((N+Q)\log N)O((N+Q)logN)。
#include <bits/stdc++.h>
#define inf 0x3f3f3f3fusing namespace std;namespace SGT {
const int Maxn=20000000;int ch[Maxn][2],sumv[Maxn],tot;inline int cpynode(int o) {
tot++;ch[tot][0]=ch[o][0];ch[tot][1]=ch[o][1];sumv[tot]=sumv[o];return tot;
}int update(int l,int r,int o,int p,int q) {
o=cpynode(o);sumv[o]+=q;if (l==r) return o;else {
int m=((l+r)>>1);if (m>=p) ch[o][0]=update(l,m,ch[o][0],p,q);else ch[o][1]=update(m+1,r,ch[o][1],p,q);return o;}
}int merge(int l,int r,int x,int y) {
if (!x) return y;if (!y) return x;int o=++tot;sumv[o]=sumv[x]+sumv[y];if (l==r) return o;else {
int m=((l+r)>>1);ch[o][0]=merge(l,m,ch[x][0],ch[y][0]);ch[o][1]=merge(m+1,r,ch[x][1],ch[y][1]);return o;}
}int query(int l,int r,int o,int p) {
if (!o) return 0;if (l==r) return sumv[o];else {
int m=((l+r)>>1);if (m>=p) return query(l,m,ch[o][0],p);else return sumv[ch[o][0]]+query(m+1,r,ch[o][1],p);}
}}vector <int> e1[200005],vt1[200005];
int num[200005];int dep[200005],fa[200005][20];
int dfn[200005],dfs_cnt;void dfs1(int x) {
dep[x]=dep[fa[x][0]]+1;dfn[x]=++dfs_cnt;for(int i=0;i<e1[x].size();i++) {
int u=e1[x][i];fa[u][0]=x;for(int j=1;j<20;j++) fa[u][j]=fa[fa[u][j-1]][j-1];dfs1(u);}
}int lca(int x,int y) {
if (dep[x]<dep[y]) swap(x,y);int d=dep[x]-dep[y];for(int i=0;i<20;i++)if ((d>>i)&1) x=fa[x][i];if (x==y) return x;for(int i=19;i>=0;i--)if (fa[x][i]!=fa[y][i]) {
x=fa[x][i];y=fa[y][i];}return fa[x][0];
}vector <int> e2[200005],vt2[200005];
int minn[200005],st[200005];void dfs2(int x,int v) {
minn[x]=inf;int id=0;if (num[x]==v) {
vt2[x].push_back(dep[x]);minn[x]=dep[x];id=x;}for(int i=0;i<e2[x].size();i++) {
int u=e2[x][i];dfs2(u,v);if (minn[u]<minn[x]) {
minn[x]=minn[u];id=u;}}for(int i=0;i<e2[x].size();i++)if (e2[x][i]!=id) vt2[x].push_back(-minn[e2[x][i]]);e2[x].clear();
}bool cmp(int x,int y) {
return dfn[x]<dfn[y];
}void build(int v) {
if (!vt1[v].size()) return;sort(vt1[v].begin(),vt1[v].end(),cmp);int top=1;st[1]=1;for(int i=0;i<vt1[v].size();i++)if (st[top]!=vt1[v][i]) {
int x=vt1[v][i];int p=lca(x,st[top]);for(;;) {
if (dep[p]<=dep[st[top-1]]) {
e2[st[top-1]].push_back(st[top]);top--;}else if (dep[p]<dep[st[top]]) {
e2[p].push_back(st[top]);st[top]=p;}else break;}if (st[top]!=x) st[++top]=x;}for(;top>1;top--) e2[st[top-1]].push_back(st[top]);dfs2(1,v);
}int root[200005];void dfs3(int x,int n) {
root[x]=0;for(int i=0;i<vt2[x].size();i++) {
int u=vt2[x][i];root[x]=SGT::update(1,n,root[x],abs(u),((u>0)?1:-1));}for(int i=0;i<e1[x].size();i++) {
int u=e1[x][i];dfs3(u,n);root[x]=SGT::merge(1,n,root[x],root[u]);}
}int main() {
minn[0]=inf;int cases;scanf("%d",&cases);for(;cases;cases--) {
SGT::tot=0;int n;scanf("%d",&n);for(int i=1;i<=n;i++) {
e1[i].clear();vt1[i].clear();vt2[i].clear();}for(int i=2;i<=n;i++) {
int x;scanf("%d",&x);e1[x].push_back(i);}for(int i=1;i<=n;i++) {
scanf("%d",&num[i]);vt1[num[i]].push_back(i);}dfs_cnt=0;dfs1(1);for(int i=1;i<=n;i++)build(i);dfs3(1,n);int m;scanf("%d",&m);int lastans=0;for(int i=1;i<=m;i++) {
int x,y;scanf("%d%d",&x,&y);x^=lastans;y^=lastans;y=min(n,dep[x]+y);printf("%d\n",lastans=SGT::query(1,n,root[x],y));}}return 0;
}
Perfect Partitions
一个很套路的题目。
容易发现,我们其实是要求出F(x)=∏i=1Q(∑j≤bixai?j)F(x)=\prod_{i=1}^{Q}(\sum_{j\leq b_i}x^{a_i\cdot j})F(x)=∏i=1Q?(∑j≤bi??xai??j)的前N+1N+1N+1次项系数。
考虑取ln?\lnln再exp?\expexp的套路,ln?F(x)=∑i=1Q(ln?(1?xai?bi+1)?ln?(1?xia))\ln F(x)=\sum_{i=1}^{Q}(\ln(1-x^{a_i \cdot{b_i+1}})-\ln (1-x^a_i))lnF(x)=∑i=1Q?(ln(1?xai??bi?+1)?ln(1?xia?)),也即若干个ln?(1?xk)\ln(1-x^k)ln(1?xk)带系数的和。注意到ln?(1?xk)\ln(1-x^k)ln(1?xk)只有前O(Nk)\mathcal O(\frac{N}{k})O(kN?)次项有用,那么对于每个kkk直接暴力对有用的项计算系数即可算出ln?F(x)\ln F(x)lnF(x)前N+1N+1N+1次项,用调和级数可以分析出来这部分是O(Nlog?N)\mathcal O(N\log N)O(NlogN)的。
算出了ln?F(x)\ln F(x)lnF(x)前N+1N+1N+1次项系数后,直接求出exp?(ln?F(x))\exp(\ln F(x))exp(lnF(x))前N+1N+1N+1次项系数即可。时间复杂度为O(Nlog?N+Q)\mathcal O(N\log N+Q)O(NlogN+Q)。
#include <bits/stdc++.h>
#define MOD 998244353using namespace std;typedef long long ll;ll pow_mod(ll x,int k) {
ll ans=1;while (k) {
if (k&1) ans=ans*x%MOD;x=x*x%MOD;k>>=1;}return ans;
}const int Maxn=1<<19;ll *w[19];void ntt_init() {
for(int i=2,t=0;i<=Maxn;i<<=1,t++) {
w[t]=new ll[i>>1];ll wn=pow_mod(3,(MOD-1)/i);w[t][0]=1;for(int j=1;j<(i>>1);j++) w[t][j]=w[t][j-1]*wn%MOD;}
}ll inv[Maxn];void pre(int n) {
inv[1]=1;for(int i=2;i<=n;i++) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
}void rev(ll *p,int len) {
int j=len>>1;for(int i=1;i<len-1;i++) {
if (i<j) swap(p[i],p[j]);int k=len>>1;while (j>=k) {
j-=k;k>>=1;}if (j<k) j+=k;}
}void ntt(ll *p,int len,int check) {
rev(p,len);for(int i=2,t=0;i<=len;i<<=1,t++)for(int j=0;j<len;j+=i)for(int k=j;k<j+(i>>1);k++) {
ll u=p[k];ll v=w[t][k-j]*p[k+(i>>1)];p[k]=(u+v)%MOD;p[k+(i>>1)]=(u-v)%MOD;}if (check==-1) {
reverse(p+1,p+len);ll nev=pow_mod(len,MOD-2);for(int i=0;i<len;i++) p[i]=(p[i]+MOD)*nev%MOD;}
}void getinv(ll *p,ll *q,int len) {
static ll t1[Maxn];if (len==1) {
q[0]=1;return;}getinv(p,q,len>>1);memcpy(t1,p,sizeof(ll)*len);memset(t1+len,0,sizeof(ll)*len);ntt(q,len<<1,1);ntt(t1,len<<1,1);for(int i=0;i<(len<<1);i++) q[i]=q[i]*(2LL-q[i]*t1[i]%MOD)%MOD;ntt(q,len<<1,-1);memset(q+len,0,sizeof(ll)*len);
}void getdif(ll *p,ll *q,int len) {
for(int i=0;i<len-1;i++) q[i]=p[i+1]*(i+1)%MOD;q[len-1]=0;
}void getint(ll *p,ll *q,int len) {
for(int i=len-1;i>0;i--) q[i]=p[i-1]*inv[i]%MOD;q[0]=0;
}void getln(ll *p,ll *q,int len) {
static ll t1[Maxn];memset(t1,0,sizeof(ll)*(len<<1));getinv(p,t1,len);getdif(p,q,len);ntt(q,len<<1,1);ntt(t1,len<<1,1);for(int i=0;i<(len<<1);i++) q[i]=q[i]*t1[i]%MOD;ntt(q,len<<1,-1);getint(q,q,len);memset(q+len,0,sizeof(ll)*len);
}void getexp(ll *p,ll *q,int len) {
static ll t1[Maxn];if (len==1) {
q[0]=1;return;}getexp(p,q,len>>1);memset(t1,0,sizeof(ll)*(len<<1));getln(q,t1,len);for(int i=0;i<len;i++) t1[i]=(p[i]-t1[i]+MOD)%MOD;t1[0]=(t1[0]+1LL)%MOD;ntt(q,len<<1,1);ntt(t1,len<<1,1);for(int i=0;i<(len<<1);i++) q[i]=q[i]*t1[i]%MOD;ntt(q,len<<1,-1);memset(q+len,0,sizeof(ll)*len);
}ll p[Maxn],q[Maxn];
ll cnt[200005];int main() {
ntt_init();int n,m;scanf("%d%d",&n,&m);int len=1;while (len<=n) len<<=1;pre(len);for(int i=1;i<=m;i++) {
int x,y;scanf("%d%d",&x,&y);cnt[x]=(cnt[x]+1LL)%MOD;if ((ll)(y+1)*x<=n) cnt[(y+1)*x]=(cnt[(y+1)*x]-1LL+MOD)%MOD;}for(int i=1;i<=n;i++)if (cnt[i]) {
for(int j=1;i*j<len;j++) p[i*j]=(p[i*j]+cnt[i]*inv[j])%MOD;}getexp(p,q,len);for(int i=1;i<=n;i++) printf("%lld ",q[i]);printf("\n");return 0;
}