牛客多校第八场 题解
C Fuzzy Graph
题意:给定一个 个节点组成的无向图,现给这些点染成红、绿、蓝三色。若一条边的两个端点颜色不同,则该边仍为黑色,否则染成两端点共同的颜色。要求:
- 基础要求:所有的点仍被黑色边连接。
- (可选)三种颜色的点数量相同。
- (可选)三种颜色中点数目最多的一个颜色,不存在该颜色的边。
需要在2、3点要求中选择一条达成。问染色方案。
解法:如果此题图是一个二分图,那么染色方案就非常的简单了——直接图进行黑白染色,对于染色数目少的颜色再分一半出去给第三个颜色即可满足第二个条件。考虑到一个图可以被视为是二分图上再加上一些别的边,因而可以考虑还是做一次黑白染色。
经过 dfs 过程的黑白染色后,我们可以得到一个 dfs 树。只要经过这一过程,我们就一定能保证第一条件成立——现在图被 dfs 树联通,树上的边一定全为黑色。那么现在的图可以被认为是 dfs 树上的边加上一些返祖边。
考虑如何达成附加条件。首先考虑条件三——颜色最多,同时没有这样颜色的边存在。不难想到,树上节点占比最大的是叶子节点,因而不妨将叶子节点的颜色作为重点关注对象。考虑叶子节点在黑白染色中的染色情况。如果存在一种染色情况,能满足黑色或者白色叶子节点充分的多,那么我们只需要稳住这部分基本盘,和这些叶子产生边颜色冲突的树上节点颜色统统换成第三色即可。
不难发现充分多的分界点为 ——即同色叶子数目超过 我们就可以采用这一方法。体现在整棵树上,最坏情况即是 。其证明非常容易:当黑色(白色同理)的节点数目超过 时,白色节点假设全为叶子,那么白色叶子至多也只有 个,那么为了连接这些白色的叶子,至多也只有 个黑色非叶节点,那么黑色的叶子至少就有 个。因而当某一颜色超过 时,直接考虑当前颜色的非叶节点,全部刷成第三色即可——这个颜色的叶子节点就已经可以保证该种颜色的点数目最多。
那么当每种颜色的节点数目都没有超过 时,上述的假设就都不成立了,我们没办法保证这种颜色的叶子节点数目是否是充分多,并且在 dfs 树上我们也无法保证当前节点和祖先节点是否通过返祖边存在颜色相同(冲突)问题——这些返祖边我们并未记录。因而这种情况考虑第二种条件——三色数目相同。此时我们就不用关心颜色冲突问题了,只需要保证图联通。这一点可以通过保证 dfs 树上的边上两端点颜色不同解决。因而只需要从深到浅的处理,如果 dfs 边上另一个节点对应颜色多了,就改成第三色,以此从深到浅贪心的处理即可。
代码中对于这一部分的处理是记录每一个深度对应的点编号,然后这个点在 dfs 树上的父亲和它颜色不能同时修改。
#include <cstdio> #include <algorithm> #include <queue> #include <memory.h> using namespace std; struct line { int from; int to; int next; }; struct line que[1000005]; vector<int> graph[300005]; int cnt, headers[300005], father[300005]; int sum[4] = {0}; void add(int from,int to) { cnt++; que[cnt].from = from; que[cnt].to = to; que[cnt].next = headers[from]; headers[from] = cnt; } int color[300005], depth[300005]; void dfs(int place,int fa) { for (int i = headers[place]; i;i=que[i].next) if(que[i].to!=fa) if(!color[que[i].to]) { father[que[i].to] = place; depth[que[i].to] = depth[place] + 1; color[que[i].to] = 3 - color[place]; sum[color[que[i].to]]++; graph[depth[que[i].to]].push_back(que[i].to); dfs(que[i].to, place); } } bool vis[300005]; int main() { int t, n, m, u, v; scanf("%d", &t); while(t--) { sum[1] = sum[2] = sum[3] = 0; scanf("%d%d", &n, &m); cnt = 0; for (int i = 1; i <= n;i++) { vis[i] = 0; graph[i].clear(); father[i] = 0; depth[i] = 0; headers[i] = 0; color[i] = 0; } for (int i = 1; i <= m;i++) { scanf("%d%d", &u, &v); add(u, v); add(v, u); } depth[1] = 1; color[1] = 1; father[1] = 1; sum[1]++; graph[1].push_back(1); dfs(1, 0); int limit = n / 3; if(sum[1]>limit*2 || sum[2]>limit*2)//满足条件三 { int ask = sum[1] > sum[2] ? 1 : 2; for (int i = 1; i <= n;i++) if (color[father[i]] == ask) color[father[i]] = 3; } else { for (int i = n; i >= 1;i--) for(auto j:graph[i]) if(!vis[j] && sum[color[j]]>limit) { sum[color[j]]--; color[j] = 3; vis[father[j]] = 1;//父亲节点和它自己不能同时改,否则树边断裂 } } for (int i = 1; i <= n;i++) switch(color[i]) { case 1: { printf("R"); break; } case 2: { printf("G"); break; } case 3: { printf("B"); break; } } printf("\n"); } return 0; }
D OR
题意:有一长度为 的序列 ,给定长度为 的序列 ,,问可能的 有多少个。
解法:一个非常重要的性质是 。因而相当于给定了 与 。这个条件是逐二进制位独立的,那么只需要枚举第一个数的每一个二进制位能填什么即可,可以推出每一项这一二进制位填什么。
整体复杂度 。
#include <cstdio> #include <algorithm> using namespace std; int b[100005], c[100005]; int main() { int n; scanf("%d", &n); for (int i = 2; i <= n; i++) scanf("%d", &b[i]);// | for (int i = 2; i <= n; i++) { scanf("%d", &c[i]); c[i] -= b[i];// & } long long ans = 1; for (int i = 30; i >= 0;i--) { int temp = 0; for (int start = 0; start <= 1;start++) { int latter=start; bool flag = 1; for (int j = 2; j <= n;j++) { //分四种情况讨论 int now; if((b[j]&(1<<i)) && (c[j]&(1<<i))) { if(latter==0) { flag = 0; break; } else now = 1; } else if((b[j]&(1<<i)) && !(c[j]&(1<<i))) { if(latter==0) now = 1; else now = 0; } else if(!(b[j]&(1<<i)) && (c[j]&(1<<i))) { flag = 0; break; } else if(!(b[j]&(1<<i)) && !(c[j]&(1<<i))) { if(latter==0) now = 0; else { flag = 0; break; } } latter = now; } temp += flag; } ans *= temp; } printf("%lld", ans); return 0; }
F Robots
题意:给定 的网格,有一些障碍物无法穿过。有三种机器人:
- 只能横着走
- 只能竖着走
- 既可以横着走又可以竖着走
组询问,问这三种中的某一种能否从 走到 。,。
解法:第一种和第二种机器人只需要 的遍历即可,考虑第三种机器人。
显然不可以对于每一个询问直接 的处理,考虑优化。首先,起始点至多只有 个,最大数据规模下也只有 个,还是小于询问个数,因而可能出现重复的起始点。因而与其暴力的对每一个询问都进行一次 bfs 不如直接一开始全部预处理出来,预处理出一个起点可以到哪个终点到达。这样 还是小于 。
但是就算这样时空还是不允许——空间复杂度也非常的高。考虑我们的 bfs 能够如何优化——使用 bitset 存储能到达的节点,然后 表示从 出发能到达的终点集合,其转移为 。这里直接带上了 的常数。然后 bfs 过程本身还有一个 的常数,因而几乎等同于 。
可是空间还是不满足。考虑使用滚动数组,更新到该节点时直接回答这一节点出发的全部终点的到着情况。这样空间复杂度就降下来一维。
整体时间复杂度 ,空间复杂度 。
#include <cstdio> #include <algorithm> #include <bitset> #include <vector> using namespace std; bitset<500 * 500> f[505]; int mp[505][505]; int n, m; struct node { int x; int y; int id; }; vector<node> query[505][505]; bool ans[500005]; int main() { int q; scanf("%d%d", &n, &m); for (int i = 1; i <= n;i++) for (int j = 1; j <= m;j++) scanf("%1d", &mp[i][j]); scanf("%d", &q); for (int i = 1; i <= q;i++) { int op, x1, x2, y1, y2; scanf("%d%d%d%d%d", &op, &x1, &y1, &x2, &y2); if(op==1) { bool flag = 0; while(x1<=n && mp[x1][y1]==0) { if(x1==x2 && y1==y2) { flag = 1; break; } x1++; } ans[i] = flag; } else if(op==2) { bool flag = 0; while(y1<=m && mp[x1][y1]==0) { if(x1==x2 && y1==y2) { flag = 1; break; } y1++; } ans[i] = flag; } else query[x1][y1].push_back((node){x2, y2, i}); } for (int i = n; i >= 1;i--) for (int j = m; j >= 1;j--) if(mp[i][j]==0) { f[j] = f[j] | f[j + 1]; f[j][(i - 1) * m + j - 1] = 1; for(auto k:query[i][j]) ans[k.id] = f[j][(k.x - 1) * m + k.y - 1]; } else f[j] = 0; for (int i = 1; i <= q;i++) if(ans[i]) printf("yes\n"); else printf("no\n"); return 0; }
H Scholomance Academy
题意:,。给定 以及 个质数 ,求 。,,。
解法:首先考虑 的性质。。,因而 存在积性,可以对每一个质数单独计算。
现在来考虑每一个质数的指数分配情况,这种情况很容易联想到生成函数。记 。
因而 的生成函数 ,所求答案即为 的 次方项系数。
此处分母可以使用线性递推求出对应系数。记 ,那么乘以 时,。该过程重复 次即可算出 。然后进行多项式求逆、多项式卷积即可,可以使用现成的模板。
放上一份封装程度较高的代码。
#include<bits/stdc++.h> #define f_in(s) freopen(s,"r",stdin) #define f_out(s) freopen(s,"w",stdout) #define inf 0x3f3f3f3f #define infl 0x3f3f3f3f3f3f3f3fll #define rep(i,a,n) for(int i=(a),__##i=(n);i<=__##i;++i) #define repe(i,a,n) for(int i=(a),__##i=(n);i<__##i;++i) #define repv(i,n,a) for(int i=(n),__##i=(a);i>=__##i;--i) #define repl(i,a,n) for(ll i=(a),__##i=(n);i<=__##i;++i) #define lowbit(x) ((x)&-(x)) #define mset(x,v) memset(x,v,sizeof(x)) #define mcpy(x,y) memcpy(x,y,sizeof(x)) #define mkp make_pair #define pii pair<int,int> #define pll pair<ll,ll> #define pdd pair<db,db> #define X first #define Y second #define LL __int128 #define ll long long #define db long double using namespace std; inline ll read(){ ll s=0,f=1; char c=getchar(); while(c<'0'||c>'9') f*=c=='-'?-1:1,c=getchar(); while(c>='0'&&c<='9') s=s*10+c-'0',c=getchar(); return s*f; } const ll P=998244353; const ll G=3; typedef vector<ll> Poly; struct Polynome{ int lim,*rev; vector<vector<int>> revs; inline ll pw(ll a,ll x){ ll ret=1; x%=P-1; for(;x;x>>=1,a=a*a%P) if(x&1) ret=ret*a%P; return ret; } inline ll inv(ll x){ return pw(x,P-2); } void SetLim(int len){ int L=0; for(lim=1;lim<=len;) lim<<=1,++L; if(!revs[L].size()){ revs[L].resize(lim); repe(i,1,lim) revs[L][i]=revs[L][i>>1]>>1|(i&1?lim>>1:0); } rev=revs[L].data(); } void NTT(Poly &a,bool op){ a.resize(lim,0); ll g=op?G:inv(G),ilim=inv(lim); repe(i,0,lim) if(i<rev[i]) swap(a[i],a[rev[i]]); for(int m=1;m<lim;m<<=1){ for(ll i=0,wn=pw(g,(P-1)/(m<<1));i<lim;i+=m<<1){ for(ll j=i,w=1,x,y;j<i+m;j++,w=w*wn%P){ x=a[j]%P,y=w*a[j+m]%P,a[j]=x+y,a[j+m]=x-y+P; } } } if(!op) repe(i,0,lim) a[i]=a[i]%P*ilim%P; else repe(i,0,lim) a[i]%=P; } public: Polynome(int maxL=30):revs(maxL){} void Int(Poly &d,const Poly &a){ d.resize(a.size()); repv(i,a.size()-1,1) d[i]=a[i-1]*inv(i)%P; d[0]=0; } void Diff(Poly &d,const Poly &a){ d.resize(a.size()); repe(i,1,a.size()) d[i-1]=a[i]*i%P; d.back()=0; } void Mul(Poly &d,Poly a,Poly b){ int len=a.size()+b.size()-1; SetLim(len),NTT(a,1),NTT(b,1),d.assign(lim,0); repe(i,0,lim) d[i]=a[i]*b[i]%P; NTT(d,0),d.resize(len); } void Inv(Poly &d,Poly a){ function<void(int)> mInv=[&](int len){ if(len==1) return d.assign(1,inv(a[0])); mInv((len+1)>>1); Poly f(a.begin(),a.begin()+len); SetLim(len<<1),NTT(f,1),NTT(d,1); repe(i,0,lim) (d[i]*=P+2-f[i]*d[i]%P)%=P; NTT(d,0),d.resize(len); }; mInv(a.size()); } void Div(Poly &d,Poly a,Poly b){ int len=a.size()-b.size()+1; if(a.size()<b.size()) return d.assign(1,0); reverse(a.begin(),a.end()),reverse(b.begin(),b.end()); b.resize(len),Inv(b,b),Mul(d,a,b),d.resize(len); reverse(d.begin(),d.end()); } void Mod(Poly &q,Poly &r,Poly a,Poly b){ Div(q,a,b),Mul(r,b,q),r.resize(b.size()-1); repe(i,0,r.size()) r[i]=(a[i]+P-r[i])%P; } void Mod(Poly &r,Poly a,Poly b){ Div(r,a,b),Mul(r,b,r),r.resize(b.size()-1); repe(i,0,r.size()) r[i]=(a[i]+P-r[i])%P; } ll Recurrence(ll n,const Poly &a,Poly f) { int k = max(2, (int)a.size()); ll ret = 0; f[0]=1,reverse(f.begin(),f.end()); auto modMul=[&](Poly &x,const Poly &y) { Mul(x,x,y),Mod(x,x,f); }; Poly b(k), t(k); b[1] = 1, t[0] = 1; for (; n; n >>= 1, modMul(b, b)) if (n & 1) modMul(t, b); repe(i, 0, k)(ret += a[i] * t[i]) %= P; return ret; } }; Polynome pol; int main() { int N=read(),t=read(),m=read(); int n=m*t+1; vector<ll> f(n); f[0]=1; repe(pos,0,t) { ll p=P-read(); repe(H, pos * m, (pos + 1) * m) repv(i, H, 0) (f[i + 1] += p * f[i]) %= P; }//计算分母各项系数 vector<ll> F, g(n), fac(n); fac[0] = 1; repe(i, 1, n) fac[i] = i * fac[i - 1] % P; auto C = [&](int m, int n) { return fac[m] * pol.inv(fac[n] * fac[m - n] % P) % P; }; repe(i,0,n) { g[i] = C(n - 1, i); if(i&1) g[i] = P - g[i]; }//g(x)=(1-x)^{mt} pol.Inv(F, f);//F=f^{-1} pol.Mul(F, F, g);//F=F*g F.resize(f.size()); F.erase(F.begin()); cout << pol.Recurrence(N - 1, F, f);//求解第 n 项系数 return 0; }
J Tree
题意:给定一棵树,甲乙二人从 两点出发,若从 节点走到 节点则 节点连带与其相连的边全部删除,记二人的得分为走过的路径长度,甲需要让二人得分差值最大化,乙需要使得差值最小化,问最终的得分差值为多少。
解法:二人谁先离开 的链,那么二人就不再互相影响了——树被切分成为了至少两部分,且二人分属不同的连通块,因而二人一定都是尽可能走他所能走的最长路径。
预处理出链上每一个节点不走链上节点所能到达的最大深度,记二人在链上所在位置为 ,甲位于 而乙位于 ,那么对于每一个人只需要查询 区间上 (对应于甲) 或 的最大值。如果最大值比当前直接走下去还要大则继续前进一步,否则直接退出这条链。
这样使用区间查询最大值可以做到 的复杂度。下面是我队在比赛中写的 的代码。
#include<iostream> #include<cstdio> using namespace std; const int N=500010; struct node{ int from,to; }edge[2*N]; int n,m,i,j,k,first[N],nxt[2*N],u,v,d[N],cnt,s,t,w[N],ans1[N],ans2[N],w2[N],f[19][N],log[N],f2[19][N],dd[N]; void addedge(int u,int v){ edge[i].from=edge[i+n-1].to=u;edge[i].to=edge[i+n-1].from=v;nxt[i]=first[u];first[u]=i;nxt[i+n-1]=first[v];first[v]=i+n-1; } bool dfs(int u,int father){bool flag;if (u==t) flag=true; else flag=false; for (int i=first[u],v;i;i=nxt[i]){v=edge[i].to;if (v!=father) flag=flag||dfs(v,u);}if (flag) d[++cnt]=u;return flag; } void dfs1(int u,int father,int x){w[u]=0;w2[u]=-1; for (int i=first[u],v;i;i=nxt[i]){v=edge[i].to;if (v!=d[x-1]&&v!=d[x+1]&&v!=father){dfs1(v,u,x);if (1+w[v]>w[u]) w2[u]=w[u],w[u]=max(w[u],1+w[v]); else if (1+w[v]>w2[u]) w2[u]=1+w[v];}} } int getit1(int l,int r){ return max(f[log[r-l+1]][l],f[log[r-l+1]][r-(1<<log[r-l+1])+1]); } int getit2(int l,int r){ return max(f2[log[r-l+1]][l],f2[log[r-l+1]][r-(1<<log[r-l+1])+1]); } int doit(int xx,int l,int r){ if (l==r){ if (xx==1) return l-1+w[d[l]]-(cnt-r); else return l-1-(cnt-r+w[d[l]]); } if (xx==1){ return max(w[d[l]]+l-1-getit2(l+1,r),doit(2,l+1,r)); }else{ return min(getit1(l,r-1)-(cnt-r+w[d[r]]),doit(1,l,r-1)); } } int main(){ scanf("%d%d%d",&n,&s,&t);for (i=1;i<=n-1;i++) scanf("%d%d",&u,&v),addedge(u,v); dfs(s,0);for (i=1;i<=cnt;i++) dd[i]=d[i];for (i=1;i<=cnt;i++) d[i]=dd[cnt-i+1]; for (i=1;i<=cnt;i++) dfs1(d[i],0,i);for (i=1;i<=cnt;i++) ans1[i]=w[d[i]]+i-1,ans2[i]=w[d[i]]+cnt-i; log[0]=log[1]=0;for (i=2;i<=cnt;i++) log[i]=log[i>>1]+1; for (i=1;i<=cnt;i++) f[0][i]=ans1[i];for (i=1;i<=18;i++) for (j=1;j<=cnt;j++) f[i][j]=max(f[i-1][j],f[i-1][j+(1<<(i-1))]); for (i=1;i<=cnt;i++) f2[0][i]=ans2[i];for (i=1;i<=18;i++) for (j=1;j<=cnt;j++) f2[i][j]=max(f2[i-1][j],f2[i-1][j+(1<<(i-1))]); printf("%d\n",doit(1,1,cnt));return 0; }
考虑另一种线性做法——倒过来处理。一开始初始位置二人在链的端点,然后不断的向两侧走。依旧时查询 之间的最大值,但是每一次由于只会走一步,因而最大值可以 的更新并查询。这样整体复杂度仅 。
#include <cstdio> #include <algorithm> using namespace std; int max_depth[2][500005]; int road[500005]; struct line { int from; int to; int next; }; struct line que[1000005]; int cnt, headers[500005]; void add(int from,int to) { cnt++; que[cnt].from = from; que[cnt].to = to; que[cnt].next = headers[from]; headers[from] = cnt; } int father[2][500005], depth[2][500005]; bool judge[2][500005]; void dfs(int id,int place,int fa) { father[id][place] = fa; depth[id][place] = depth[id][fa] + 1; max_depth[id][place] = 0; for (int i = headers[place]; i;i=que[i].next) if(que[i].to!=fa) { dfs(id, que[i].to, place); judge[id][place] |= judge[id][que[i].to]; if(!judge[id][que[i].to]) max_depth[id][place] = max(max_depth[id][place], max_depth[id][que[i].to] + 1); } } int main() { int n, s, t, u, v; scanf("%d%d%d", &n, &s, &t); for (int i = 1; i < n;i++) { scanf("%d%d", &u, &v); add(u, v); add(v, u); } judge[0][t] = judge[1][s] = 1; dfs(0, s, 0); dfs(1, t, 0); int place = s, cnt = 0; while(place) { road[++cnt] = max_depth[0][place]; place = father[1][place]; } int left = cnt - cnt / 2, right = left + 1; bool now = cnt & 1; int max_left = left - 1 + road[left]; int max_right = cnt - right + road[right]; int ans = max_left - max_right; while (left > 1 || right < cnt) { if(now) { max_right = max(max_right, cnt - left + road[left]); left--; ans = max(ans, left - 1 + road[left] - max_right); max_left = max(max_left, left - 1 + road[left]); } else { max_left = max(max_left, right - 1 + road[right]); right++; ans = min(ans, max_left - (cnt - right + road[right])); max_right = max(max_right, cnt - right + road[right]); } now ^= 1; } printf("%d", ans); return 0; }