2023-08-05 16:22:13
总结
这次打下来,由于 T2 贪心不够完全,T3 模拟 \(5\) 个时不是最优,T4 想到暴力做法但是来不及打,加之全都是捆绑测试点,导致我 T2,T3 虽然加起来有不少点对了,但是还是判全错,最后也只剩下 T1 的 100。
感觉这次前三题也不难,都是可做的,T4 的 30pts 暴力也很白给,但是我想加调了两个小时 T2 的假树形 dp,然后就来不及打了。
赛后发现 T2 的贪心和我想的基本一致,但是枚举的东西略有不同,也就是我的不够贪心。T3 的构造不难想,有一个挺显然的性质但是我并没有去在意。T1 有可以不用数位 dp 的更好方法,但我也没去找了。
总而言之,下次可以不需要这么心急,可以先打个暴力当对拍都不错,不过也挺好了,感觉自己的算法思维也越来越强了呢,还是有不错的提升的。
T4 的正解因为我还没有看懂,所以就不放了,等我自己想明白了再来搞。
那还是先进入正题吧~
A
题目描述
有 \(n+m\) 堆石子,\(n\) 堆中第 \(i\) 堆有 \(i\) 个石子,\(m\) 堆中第 \(i\) 堆中有 \(a_i\) 堆石子,问你进行 \(nim\) 游戏时先手第一步有多少种取法能保证自己获胜。(第一步之后均采用最优策略)。
\(1\le n\le 10^{18},1\le m\le 10^5,1\le a_i\le 10^{18}\)。
思路
令 \(sum=\bigoplus_{i=1}^{n}i\ \oplus\bigoplus_{i=1}^{m}a_i\)(即所有元素的亦或和)。
因为我上课的时候证明 \(nim\) 游戏的正确性的时候没有认真听,所以一开始以为我从大于等于 \(sum\) 的数中取 \(sum\) 个即可。但是显然用脑子想想都发现不对,我甚至还因为这个去问出题人要样例的解释,后来自己模拟一遍才发现不对劲,完全不是一个东西。
思考正解,设我们选了取个数为 \(x\) 的一堆,假设取后是 \(x'\) 那么 \(sum'=sum\oplus x \oplus x'\),因为要保证必胜,学过 \(nim\) 的都知道最后要保证 \(sum'=0\),所以我们也可以得到: \(sum\oplus x=x'\)。因为是取后,所以 \(x'<x\),即 \(sum\oplus x<x\)。
所以最终只需要求 \(sum\oplus x<x\) 的数的个数即可。
对于 \(m\le 10^5\),我们直接枚举 \(a_i\) 即可,但是 \(n\leq 10^{18}\),总不能一个一个枚举吧?
此时的瓶颈有两个了,第一个是 \(n\) 堆的异或和不好求,第二个是答案也不好求。
首先解决异或的问题,我们通过打表可以得到一个结论,当 \(n \mod 4=3\) 时,\(n\) 的前缀异或和为 \(0\)。
证明也很简单:
我们设 \(n\) 是 \(4\) 的倍数,那么二进制下:
\[n=?????00 \]\[n+1=?????01 \]\[n+2=?????10 \]\[n+3=?????11 \]显然 \(n\oplus(n+1)\oplus(n+2)\oplus(n+3)=0\),得证。
设 \(sum\) 最高位是第 \(k\) 位。
然后再通过分类讨论可以得到只有当 \(x\) 的第 \(k\) 位为 \(1\) 时满足条件。
所以机智的我懒得思考有的没的,直接现场 \(3\) 分钟打了一个数位 dp。
\(Code\)
ll n,m,sum,k,a[100005],f[2][2][102],A[102];
ll dfs(ll len,ll lim,ll isk){
if(~f[lim][isk][len])return f[lim][isk][len];
if(!len)return f[lim][isk][len]=isk;
ll maxx=lim?A[len]:1,res=0;
for(ll i=0;i<=maxx;i++){
if(len==k+1)res+=dfs(len-1,lim&(i==maxx),i==1);
else res+=dfs(len-1,lim&(i==maxx),isk);
}
return f[lim][isk][len]=res;
}
ll solve(ll x){
memset(f,-1,sizeof(f));
ll cnt=0;
while(x){
A[++cnt]=x&1;
x>>=1;
}
return dfs(cnt,1,0);
}
int main(){
n=read(),m=read();
for(ll i=1;i<=m;i++){
a[i]=read();
sum^=a[i];
}
if(n%4==0)sum^=n;
else if(n%4==1)sum^=(n-1)^n;
else if(n%4==2)sum^=(n-2)^(n-1)^n;
if(!sum)puts("0");
else{
ll ans=0;
k=__lg(sum);
for(ll i=1;i<=m;i++){
if(a[i]&(1ll<<k))ans++;
}
ans+=solve(n);
cout<<ans;
}
}
求 \(1\sim n\) 中的答案其实是可以推式子的,我们把二进制的 \(n\) 分成 \([1,k-1],[k+1,k_n]\)两个部分,当 \(n\) 的第 \(k\) 为 \(1\) 时,答案就是两部分的总方案数相乘,如果为 \(0\),那也只有 \([k+1,k_n]\) 为原部分的方案数不行。
核心代码
ll ans=0;
k=__lg(sum);
for(ll i=1;i<=m;i++){
if(a[i]&(1ll<<k))ans++;
}
ans+=(n>>k+1ll)*(1ll<<k);//先把不含自己的方案数算上
ans+=max(n%(1ll<<k+1ll)-(1ll<<k)+1ll,0ll);//再加上如果 k 位有1的方案数
cout<<ans;
总得来说还是简单的,但是因为好不容易找出正解了考场上打出来也是挺开心的(打的比较细心,基本上就微调过大样例),后来发现原来不需要这样麻烦,甚至有些神仙通过打表找规律找到正解,悲伤。(郑神!!!%%%%)
B
问题描述
给你一棵树,每个节点得染成红色或者蓝色,\(i\) 点染成红色的代价为 \(a_i\),染成蓝色为 \(b_i\),最后必须保证对于每个点,它的子树内的点(含己)上面的红点总数和蓝点总数的差的绝对值不超过 \(1\),求最小代价。
\(3\le n\le 10^6\)
思路
一眼树形 dp,但是不知道具体怎么做,开始是想把每个儿子节点看成物品然后跑背包的,奈何本人技术力不够打不来这种背包,然后就没想着去拿这 \(O(n^2)\) 的 \(40\) 分。
还是看限制条件吧,“对于任意一个点,子树内\(\dots\)”,那我们不难发现对于每个子树只有三种情况:\(R=B,R=B+1,R=B-1\)(\(R\) 表示子树内红色点个数,\(B\) 表示子树内红色点个数)。
并且,我们观察到如果一个子树的大小是偶数,那只可能出现蓝色等于红色的情况,这种情况因为蓝色等于红色,怎么加都不会不合法,所以保证代价最小即可,不用理会。
而当大小为奇数时,有两种情况。
所以我们设计状态 \(f[0(R=B)/1(R=B+1)/2(R=B-1)][u]\) 表示以 \(u\) 为根节点的子树,红蓝配比为 \((R=B)/(R=B+1)/(R=B-1)\) 的最小代价,仔细思考不难发现转移:
\[f[0][u]=min(ans_{R=B+1}+b[u],ans_{R=B-1}+a[u])\\ f[1][u]=min(ans_{R=B+2}+b[u],ans_{R=B}+a[u])\\ f[2][u]=min(ans_{R=B-2}+a[u],ans_{R=B}+b[u])\]其中 \(ans_{case}\) 表示没加入根时子树的颜色情况为 \(case\) 时的最小代价。
那 \(ans_{case}\) 答案怎么求呢?首先如果儿子的子树大小为偶,直接加入贡献即可,因为此时加入不会对颜色的情况造成影响,然后对奇数大小的儿子贪心取最优即可,贡献可以表示为:
\[ans_{R-B=x}=\sum_{v\in son[偶]_u}f[0][v]+min\{\sum_{i=1,v\in son[奇]_u}^{kr}f[1][v]+\sum_{i=1,v\in son[奇]_u}^{kb}f[2][v]\} \]其中 \(kr+kb=sum_{奇子树个数},kr-kb=x\)。
怎么贪心取最优?我们假设先全选蓝色,然后把红色减蓝色的贡献加入优先队列,取最小的即可。
\(Code\)
int n; // 0:R=B 1:R>B 2:R<B
ll a[1000006],b[1000006],f[3][1000006],siz[1000006];
vector<int>F[1000006];
void dfs(int x,int fa){
priority_queue<ll,vector<ll>,greater<ll> >Q;
siz[x]=1;
ll ans=0;
for(int y:F[x]){
if(y==fa)continue;
dfs(y,x);
siz[x]+=siz[y];
if(siz[y]&1){
ans+=f[2][y];
Q.push(f[1][y]-f[2][y]);
}else ans+=f[0][y];
}
if(!Q.size()){
f[1][x]=ans+a[x],f[2][x]=ans+b[x];
return;
}
if(siz[x]&1){
//奇数的时候,一定有偶数个子树不是偶siz
//要么是少两个b然后选b,要么少两个a选a,要么不多不少选a或者选b
//两种答案,各分两种情况讨论。
int cnt=Q.size()>>1;//枚举选几个a,不减1是持平的
cnt--;
while(cnt--){
ans+=Q.top(),Q.pop();
}
f[2][x]=min(ans+a[x],ans+Q.top()+b[x]);
ans+=Q.top();
Q.pop();
f[1][x]=min(ans+a[x],ans+Q.top()+b[x]);
}else{
//偶数的时候,一定有奇数个子树不是偶siz
//要么是少a选b,要么是少b选a
int cnt=Q.size()>>1;//枚举选几个a,这里是先少选a的
while(cnt--){
ans+=Q.top(),Q.pop();
}
f[0][x]=min(ans+a[x],ans+Q.top()+b[x]);
}
}
int main(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read(),b[i]=read();
}
for(int i=1;i<n;i++){
int u=read(),v=read();
F[u].push_back(v);
F[v].push_back(u);
}
dfs(1,0);
ll ans;
if(n&1)ans=min(f[1][1],f[2][1]);
else ans=f[0][1];
cout<<ans;
}
一开始其实思路跟这个差不多,但是由于我不想开三维,就开了一维然后分奇偶讨论,天真地以为奇数大小的子树中除根的节点必然满足红色等于蓝色,然后方法差不多,直接把奇数情况的 \(a[v]-b[v]\) 扔进优先队列了,没想到其实与根的颜色关系不大,要看的是子树的颜色,然后就全 WA 了(邪恶的捆绑测试点)。
下次讨论 dp 的时候不要想当然,要关注整体的贡献而不是个体的贡献(根节点的颜色不影响整体,看得是子树多的颜色),多思考,反思,看题也要仔细(一开始以为不算根)!!
C
分类讨论大挂了,不过问题不大,其实也想到了一些构造思想,但是没想到那么显然。
题意:
给定一个 \(n\) 个点的完全图,你要给每条边定一个方向,使得得到的有向完全图(又称竞赛图)的三元环个数最多。(\(n\le 1000\))
思路
观察图中任意三个点的关系,发现要么是三元环,要么就是一个点指向另外两个点。三元环的个数不好算,但是第二个的个数好算,我们令 \(i\) 点的出度为 \(deg_i\),那么非三元环的个数就是 \(\sum\limits_{i=1}^{n}\binom{deg_i}{2}\),三元环的个数即为:\(\binom{n}{3}-\sum\limits_{i=1}^{n}\binom{deg_i}{2}\)。而:
\[\sum\limits_{i=1}^{n}\binom{deg_i}{2}=\frac{1}{2}\sum\limits_{i=1}^{n}(deg_i-1)deg_i=\frac{1}{2}\sum\limits_{i=1}^{n}deg_i^2-\frac{1}{2}\sum\limits_{i=1}^{n}deg_i=\frac{1}{2}\sum\limits_{i=1}^{n}deg_i^2-\frac{n(n-1)}{4} \]所以只需要最小化 \(\sum\limits_{i=1}^{n}deg_i^2\) 即可,观察到一个点的出度与入度越接近,这个值就越小。
提供两个构造方法:
- 奇数个点时显然每个点可以做到有 \(\frac{n-1}{2}\) 个出度,然后偶数个只需要在奇数个的基础上加一个点平摊出度即可
\(Code_1\)
m=n;
if(n+1&1)m--;
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
if(i!=j&&((m-i+j)<=(m-1>>1)||(j-i<=(m-1>>1)&&j>i)))
a[i][j]=1;
}
}
if(n+1&1){
for(int i=1;i<=(m>>1);i++)a[i][n]=1;
for(int i=(m>>1)+1;i<=m;i++)a[n][i]=1;
}
for(int i=1;i<=n;i++,puts("")){
for(int j=1;j<=n;j++){
printf("%d",a[i][j]);
}
}
- 就是隔一个连一个,这样基本可以平摊出度。
\(Code_2\)
int n=read();
for(int i = 1;i<=n;i++) for(int j = i+1,t=1;j<=n;j++) a[i][j]=t,t=1-t;
for(int i = 1;i<=n;i++) for(int j = i+1,t=0;j<=n;j++) a[j][i]=t,t=1-t;
for(int i = 1;i<=n;i++){for(int j = 1;j<=n;j++)cout<<a[i][j];cout<<"\n";}
然后我目前听得懂的题目就这么多了,把 D 放出来让大佬们思考一下。
D
问题描述
给定一个 \(n\) 个点的树 \(G\),称它的一个子图 \(G′\) 为“子树”当且仅当 \(G=G′\),或者存在 \(G\) 上的一条边,将 \(G\) 断掉这条边之后 \(G′\) 是分成的两个连通块之一。
对于所有的 \(k=1,2,3\dots n\),求从这棵树中等概率选出 \(k\) 个不同的点,包含它们的最小的“子树”的大小的期望模 \(998244353\) 的值。
\(3\le n\le 7000\)。
样例
in:
5
1 2
1 3
2 4
2 5
out
798595484
499122180
4
199648875
5
思路
我只想到了 \(n\le 15\) 时,枚举断掉的边和选中的点,然后搜索即可。
标签:le,ll,int,题解,sum,集训,ans,Day19,deg From: https://www.cnblogs.com/NBest/p/17686910.html