水题记录2
文章目录
- CF1521D Nastia Plays with a Tree
- luogu P12576 [UOI 2021] 数字图
- luogu P12445 [COTS 2025] 数好图 / Promet
- CF1511G Chips on a Board
CF1521D Nastia Plays with a Tree
link
题目:
考虑一种贪心:
看这个点有几个儿子:
- 若只有一个则什么也不用干。
- 否则如果这个节点不是根节点,切断其与父亲的连边。
- 然后再随便选若干条边切掉,使得这个节点一共最多只与两条边相连。
证明感性理解:递归完所有子树后,所有子树都是处理好的链,那么对于当前节点 uuu,如果切断其与父亲的连边则对父亲有贡献,不切则没有贡献。
那么显然同时对自己和父亲有贡献更优一点。
那么这题就做完了。
不过需要输出路径,删的很好考虑,加边的话考虑对于所有联通块都搜索一遍,找到两个叶子节点,加边就是这个联通块的某个叶子节点和上一个联通块的叶子节点连边即可。
精细实现可以做到 O(n)O(n)O(n),不过为了省事代码使用了 map
,复杂度为 O(nlogn)O(n\log n)O(nlogn)。
代码:
void dfs(int u,int pa){int sz=E[u].size();if(pa!=0)sz--;for(int v:E[u]){if(v==pa)continue;dfs(v,u);if(us[v])sz--;}if(sz<=1)return ;if(u!=1){del.push_back({u,pa});us[u]=1;deg[u]--,deg[pa]--;A[{u,pa}]=A[{pa,u}]=-1;}if(sz>2){for(int v:E[u]){if(v==pa||us[v])continue;del.push_back({v,u});us[v]=1;deg[v]--,deg[u]--;A[{u,v}]=A[{v,u}]=-1;sz--;if(sz==2)break;}}
}
vector<int>le;
void dfs1(int u,int pa){vis[u]=1;for(int v:E[u]){if(v==pa||A[{v,u}]==-1)continue;dfs1(v,u);}if(deg[u]<=1)le.push_back(u);
}
void solve(){A.clear();add.clear(),del.clear();cin>>n;for(int i=1;i<=n;i++)E[i].clear(),deg[i]=us[i]=vis[i]=0;for(int i=1;i<n;i++){int x,y;cin>>x>>y;E[x].push_back(y);E[y].push_back(x);deg[x]++,deg[y]++;}dfs(1,0);for(int i=1;i<=n;i++){if(!vis[i]){le.clear();dfs1(i,0);if(le.size()==1)le.push_back(le[0]);add.push_back({le[0],le[1]});}}cout<<del.size()<<"\n";for(int i=0;i<del.size();i++)cout<<del[i].first<<" "<<del[i].second<<" "<<add[i].second<<" "<<add[i+1].first<<"\n";
}
luogu P12576 [UOI 2021] 数字图
lnik
题目:
显然答案具有单调性,考虑二分答案 xxx。
把 ≥x\ge x≥x 的点记为 111 类点,否则记为 000 类点。
则判定可以简化成一个博弈论。
考虑将所有端点种类不一样的边抽出来,显然只有这些边是可能会被有效操作的。
因为先手想从 0→10\rightarrow 10→1,后手相反。
考虑使用 SGSGSG 函数,没有转移边的点肯定必输,其 SGSGSG 函数值为 000。
考虑转移,经典有向图游戏,若 uuu 的后继状态有一个必输,则当前节点必胜,若全为必胜,则当前节点必输。
注意有可能状态不会被确定,也就是在环里一直转。
注意到游戏步数为偶数,所以只有 111 号点是必胜态时先手才会获得胜利。
那么就搞定了,时间复杂度为 O(nlogV)O(n\log V)O(nlogV)。
注意如果 111 号节点是 111 类点,先手可以直接结束游戏获得胜利。
代码:
vector<int>E[N],e[N],e1[N];
int n,m,a[N],sg[N],p[N],ex[N],ey[N],deg[N];
bool check(int k){for(int i=1;i<=n;i++)p[i]=(a[i]>=k),sg[i]=-1,deg[i]=0,e[i].clear(),e1[i].clear();if(p[1]==1)return 1;for(int i=1;i<=m;i++){int x=ex[i],y=ey[i];if(p[x]==p[y])continue;e[y].push_back(x);e1[x].push_back(y);deg[x]++;}queue<int>Q;for(int i=1;i<=n;i++){if(!deg[i])sg[i]=0,Q.push(i);}while(!Q.empty()){int u=Q.front();Q.pop();for(int v:e[u]){if(sg[v]==-1){if(sg[u]==0){sg[v]=1;Q.push(v);}else{deg[v]--;if(!deg[v]){sg[v]=0;Q.push(v);}}}}}return sg[1]==1;
}
void solve(){cin>>n>>m;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=m;i++){int x,y;cin>>x>>y;E[x].push_back(y);ex[i]=x,ey[i]=y;}int l=a[1],r=1e9+10;int ans=a[1];while(l<=r){int mid=l+r>>1;if(check(mid))ans=mid,l=mid+1;elser=mid-1;}cout<<ans<<"\n";
}
luogu P12445 [COTS 2025] 数好图 / Promet
link
题目:
神仙题。
考虑 n=kn=kn=k 时怎么做。
先解决 i,ni,ni,n 联通,考虑让每一个 iii 向后面若干个点连边,不能不连。
那么这样肯定保证所有的 iii 能到达 nnn,但是肯定还有不合法的情况,因为没有保证 111 能到达 iii。
考虑什么时候 111 不能到达 iii,显然当且仅当 iii 的入读为 000。
考虑 DPDPDP,f[i,j]f[i,j]f[i,j] 表示从倒数第 iii 个到 nnn 中,倒数第 iii 个点入读不为 000 ,且一共有 jjj 个节点入读为 000 时的方案数。
转移考虑 iii 是不是入读为 000 的点。显然不管 iii,它可以往后面的 i−1i-1i−1 个点中入读不为 000 的 i−j−1i-j-1i−j−1 个点连若干条边。
即:
f[i,j]=f[i−1,j−1]⋅(2i−j−1−1)+f[i−1,j]⋅(2i−j−1−1)f[i,j]=f[i-1,j-1]\cdot(2^{i-j-1}-1)+f[i-1,j]\cdot (2^{i-j-1}-1) f[i,j]=f[i−1,j−1]⋅(2i−j−1−1)+f[i−1,j]⋅(2i−j−1−1)
那么考虑计算 f1[i]f1[i]f1[i] 表示 n=i,k=in=i,k=in=i,k=i 时的答案。
可以用容斥,有:
f1[i]=∑j=0i−1(−1)jf[i,j]f1[i]=\sum_{j=0}^{i-1}(-1)^jf[i,j] f1[i]=j=0∑i−1(−1)jf[i,j]
考虑从 n=k,kn=k,kn=k,k 推到 n,kn,kn,k。
显然我们需要加入一些点。
将点分成三类:
- 一类点:111 能到 iii,iii 能到 nnn,即原图中的点。
- 二类点:111 能到 iii,iii 不能到 nnn。
- 三类点:111 不能到 iii,iii 能不能到 nnn 随意。
考虑计数三类点,记 g[i,j]g[i,j]g[i,j] 表示 [i,n][i,n][i,n] 中有 jjj 个三类点。
转移考虑 iii 是不是三类点。而三类点对于后面的连边没有任何限制。所以可得:
g[i,j]=g[i+1,j]+g[i+1,j−1]⋅2n−ig[i,j]=g[i+1,j]+g[i+1,j-1]\cdot 2^{n-i} g[i,j]=g[i+1,j]+g[i+1,j−1]⋅2n−i
接着是二类点。
发现二类点前边至少连着一个一类点或二类点,考虑从前往后 DPDPDP。
记 h[i,j]h[i,j]h[i,j] 为到第 i+1i+1i+1 个一类点之前(不含第 i+1i+1i+1 个一类点),一共有 jjj 个二类点。
转移考虑 jjj 个二类点的位置。
有:
h[i,j]=h[i−1,j]+h[i,j−1]⋅(2i+j−1−1)h[i,j]=h[i-1,j]+h[i,j-1]\cdot (2^{i+j-1}-1) h[i,j]=h[i−1,j]+h[i,j−1]⋅(2i+j−1−1)
但是你会发现由于 nnn 号点一定是一类点,所以我们需要拆开转移:
h[i,j]=h[i−1,j]h[i,j]=h[i,j−1]⋅(2i+j−1−1)h[i,j]=h[i-1,j]\\ h[i,j]=h[i,j-1]\cdot (2^{i+j-1}-1) h[i,j]=h[i−1,j]h[i,j]=h[i,j−1]⋅(2i+j−1−1)
显然转移完第一条后刚好符合最后一个节点是一类点。
那么此时贡献给 ansians_iansi 表示 k=ik=ik=i 时的答案。
有:
ansi=∑jf1[i]⋅h[i,j]⋅g[1,n−i−j]ans_i=\sum_jf1[i]\cdot h[i,j]\cdot g[1,n-i-j] ansi=j∑f1[i]⋅h[i,j]⋅g[1,n−i−j]
而对于 k=1k=1k=1,答案为 000,这里不做证明。
对于 k=0k=0k=0,考虑图的总数 2n(n−1)/22^{n(n-1)/2}2n(n−1)/2 减去所有 k≥2k\ge 2k≥2 的情况即可。
那么这题就做完了,时空复杂度均为 O(n2)O(n^2)O(n2)
不过好像能优化成 O(nlog2n)O(n\log^2 n)O(nlog2n),但是我比较菜。
就这样。
inline int qmi(int x,int y){int res=1;while(y>0){if(y&1)res=res*x%p;x=x*x%p;y>>=1;}return res;
}
void solve(){cin>>n>>p;pw2[0]=1,p2[0]=0;for(int i=1;i<N;i++)pw2[i]=pw2[i-1]*2%p,p2[i]=pw2[i]-1;f[1][0]=1;for(int i=2;i<=n;i++){for(int j=0;j<i;j++)f[i][j]=f[i-1][j]*p2[i-1-j]%p+f[i-1][j-1]*p2[i-1-j]%p,f[i][j]%=p;}for(int i=2;i<=n;i++)for(int j=0;j<i;j++){f1[i]=(f1[i]+(j&1?-1:1)*f[i][j]%p)%p;}g[n][0]=1;for(int i=n-1;i>=2;i--){for(int j=0;j<=n-i;j++){g[i][j]=g[i+1][j]+g[i+1][j-1]*pw2[n-i];g[i][j]%=p;}}h[0][0]=1;for(int i=1;i<=n;i++){for(int j=0;j<=n-i;j++){h[i][j]=h[i-1][j];ans[i]=(ans[i]+f1[i]*h[i][j]%p*g[2][n-i-j]%p)%p;}ans[i]=(ans[i]%p+p)%p;for(int j=1;j<=n-i;j++)h[i][j]+=h[i][j-1]*p2[i+j-1]%p,h[i][j]%=p;}int res=qmi(2,n*(n-1)/2);for(int i=2;i<=n;i++)res-=ans[i],res%=p;res=(res%p+p)%p;cout<<res<<" "<<0<<" ";for(int i=2;i<=n;i++)cout<<ans[i]<<" ";
}
CF1511G Chips on a Board
link
题目:
从没见过的倍增的新玩法。
其实这题是可以暴力做的,有 CF的老哥 O(n2)O(n^2)O(n2) 过了这题,且抗住了一百多发 hack 数据。
简直无法战胜。
还有神秘的莫队套 01trie 维护 +1,−1+1,-1+1,−1 操作和全局异或和。成功用 O(nnlogn)O(n\sqrt n\log n)O(nnlogn) 的复杂度通过此题。
这里讲的是一只 log\loglog 的倍增做法。
化简题目,可发现每一局都是 nim 游戏,直接使用结论,那么题目就是在求:
⊕ai∈[l,r](ai−l)\large \oplus_{a_i\in[l,r]} (a_i-l) ⊕ai∈[l,r](ai−l)
经典套路,拆位考虑。
记 f[i,j]f[i,j]f[i,j] 表示 [i,i+2j)[i,i+2^j)[i,i+2j) 中所有元素减去 iii 的异或和。
这个值显然比 2j2^j2j 小。
考虑它如何转移,正常是看 f[i,j−1]f[i,j-1]f[i,j−1] 和 f[i+2j−1][j−1]f[i+2^{j-1}][j-1]f[i+2j−1][j−1],但是你会发现后面的部分所有元素都会加上一个 2j−12^{j-1}2j−1。
但是你又会发现,f[i+2j−1][j−1]f[i+2^{j-1}][j-1]f[i+2j−1][j−1] 记录的所有元素都比 2j−12^{j-1}2j−1 小,也就是说,加上了 2j−12^{j-1}2j−1 并不影响它权值小于 2j−12^{j-1}2j−1 的位。
那么它的贡献是独立出来的!
考虑辅助数组 g[i,j]=∑ai∈[i,i+2j)1g[i,j]=\sum_{a_i\in[i,i+2^{j})} 1g[i,j]=∑ai∈[i,i+2j)1,即 [i,i+2j)[i,i+2^j)[i,i+2j) 中元素个数。
那么有转移:
g[i,j]=g[i,j−1]+g[i+2j−1,j−1]f[i,j]=f[i,j−1]⊕f[i+2j−1,j−1]⊕((g[i+2j−1,j−1]&1)×2j−1)g[i,j]=g[i,j-1]+g[i+2^{j-1},j-1]\\ f[i,j]=f[i,j-1]\oplus f[i+2^{j-1},j-1]\oplus ((g[i+2^{j-1},j-1]\& 1)\times2^{j-1}) g[i,j]=g[i,j−1]+g[i+2j−1,j−1]f[i,j]=f[i,j−1]⊕f[i+2j−1,j−1]⊕((g[i+2j−1,j−1]&1)×2j−1)
计算答案则从高位到低位考虑,和转移时差不多。
具体解释为不断右移 lll 来判断当前区间是否可以比 2j2^j2j 大,并计算答案。
代码:
void solve(){cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];g[a[i]][0]++;}for(int j=1;j<20;j++)for(int i=1;i+(1<<j)-1<=m;i++){g[i][j]=g[i][j-1]+g[i+(1<<j-1)][j-1];f[i][j]=f[i][j-1]^f[i+(1<<j-1)][j-1]^((g[i+(1<<j-1)][j-1]&1)*(1<<j-1));}cin>>q;while(q--){int l,r,len;cin>>l>>r;len=r-l+1;int ans=0,sum=0;for(int i=19;i>=0;i--){if((len>>i)&1){ans^=f[l][i]^((g[l][i]&1)*sum);l+=1<<i;sum+=1<<i;}}if(ans==0)cout<<"B";elsecout<<"A";}
}