2022NOIP联测8
T1【博弈论:公平游戏】给出n堆石子,A和B轮流操作,A每次可以选择>=1堆石子同时取走x个,B每次可以选择>=1堆石子同时取走y个,求最优决策下谁一定会赢。(n<=1e5)
当ai<x+y,问题变得非常简单,因为对于面临这个局面的选手op,如果存在 ,那么它一定输(它拿不掉,如果可以拿掉其他的,!op拿走ai后(一定可以拿),ai<val[op],op一定输;如果拿不掉其他的,这局就输了)。
考虑对于ai>=x+y的局面,抽象的假设最后S赢了,那么每局倒推回去,无论!S出什么,S都进行对应操作使得一对操作是(x+y)的效果,所以是等效的。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#define rint register int
#define _f(i,a,b) for(rint i=(a);i<=(b);++i)
#define f_(i,a,b) for(rint i=(a);i>=(b);--i)
#define ll long long
#define ull unsigned ll
#define lll __int128
#define ullll signed __int128
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
inline void wr(ll x)
{
if(x<0)
{
x=-x;putchar('-');
}
if(x>9)wr(x/10);
putchar(x%10+'0');
}
const int N=5e5+100;
int T,n,val[2],who,a[N];
inline void Deal(int p)
{
int del=0,pos=0,hw=0;
//chu("cheat:%d what:%d %d %d\n",p,a[1],a[2],a[3]);
_f(i,1,n)
{
if(a[i]>=val[p])
{
++del,pos=i;
if(a[i]-val[p]>=val[!p])hw++;
}
else if(a[i]>=val[!p])hw++;
}
//chu("hw:%d del:%d\n",hw,del);
if(!del)
{
who=!p;return;//它输了
}
else if(!hw)//它可以,但是对手不行
{
who=p;return;
}
else
{
if(del==1)
{
a[pos]-=val[p];
Deal(!p);
}
else
{
bool you=0;
_f(i,1,n)
{
if(a[i]>=val[p])
{
if(a[i]>=val[1]+val[0])//如果没有这样的,而且后手还可以,那么它输了
{
//chu("in:%d\n",i);
if(a[i]>=2*val[p]+val[!p]||you)
{
a[i]-=val[p];
you=1;
}
else you=1;//如果不删除就有1个le
}
else a[i]-=val[p];
}
}
// chu("you:%d\n",you);
if(!you)
{
who=!p;
return;
}
Deal(!p);
}
}
}
int main()
{
//freopen("t1.in","r",stdin);
//freopen("1.out","w",stdout);
T=re();
while(T--)
{
n=re(),val[1]=re(),val[0]=re();
who=-1;
_f(i,1,n)a[i]=re();
_f(i,1,n)a[i]=a[i]%(val[0]+val[1]);
Deal(1);
chu("%d\n",who);
}
return 0;
}
/*
5
2 1 1
3 3
2 1 2
3 3
5 4 3
3 5 3 1 4
5 1 3
5 4 1 5 4
5 40799 4859
710399195 674416476 584161395 304319939 711921095
1
0
0
1
0
1
2 1 2
5 4 3
对于x
统计所有可以删除的元素(>=x):
[1]>0,
[1]如果所有删除后ele<y,赢
[2]
[1]=1:删除
[2]>1:Mx>=x+y:[1]Mx<2*x+y,它不能操作[2]Mx>=2*x+y,可以删除,就删除
[2]=0,输
*/
T2【区间众数应用】给出n的序列,cori<=n,每次可以删除一对相邻的数(a,b),当且仅当cora!=corb.最后剩下的数必须相同,求最少删除的数数量。(n<=2000)
考场
枚举颜色,划分块,然后就瞎删了,因为不会判断[l,r]区间什么时候可以完全删除。
正解
[l,r]可以删除:
【1】区间众数<=length/2
【2】区间长度偶数
预处理d[i][j]表示是否可以完全删除
点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念,极致,不念过往,不放当下
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define inf 2147483647
#define ll long long
#define rint register int
#define ull unsigned long long
#define lll __int128
#define ulll unsigned __int128
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
inline void wr(ll x)
{
if(x<0)
{
x=-x;putchar('-');
}
if(x>9)wr(x/10);
putchar(x%10+'0');
}
const int N=5e4+100;
bool d[2100][2100];
int T,n;int a[2100],buc[2100],f[2100];
int main()
{
//freopen("t2.in","r",stdin);
//freopen("1.out","w",stdout);
T=re();
memset(f,-1,sizeof(f));
while(T--)
{
_f(i,0,n)_f(j,0,n)d[i][j]=0;
fill(f+0,f+1+n,-1);
n=re();
_f(i,1,n)a[i]=re();
_f(i,1,n)//枚举起点
{
int Mx=0;
fill(buc+1,buc+1+n,0);
_f(j,i,n)
{
int len=j-i+1;
buc[a[j]]++;
if(buc[a[j]]>buc[Mx])Mx=a[j];//维护众数
if(!(len&1))//是偶数
{
d[i][j]=(buc[Mx]<=len/2);//可以删除
}
}
}
f[0]=0;
_f(i,1,n+1)d[i][i-1]=1;
//_f(i,1,n)_f(j,i,n)chu("d[%d][%d]:%d\n",i,j,d[i][j]);
// chu("%d\n",f[3]);
_f(i,1,n)//枚举f[i]:ai是剩下的数,最多剩下的
{
_f(j,0,i-1)//找剩下的数
{
if((j==0||a[j]==a[i])&&d[j+1][i-1])
{
if(f[j]!=-1)f[i]=max(f[i],f[j]+1);//chu("update:%d from:%d\n",i,j);
}
}
//chu("f[%d]:%d\n",i,f[i]);
}
int ans=0;
_f(i,1,n)
{
if(f[i]!=-1&&d[i+1][n])
{
// chu("%d is yes\n",i);
ans=max(ans,f[i]);
}
}
chu("%d\n",n-ans);
}
return 0;
}
/*
1
7
3 3 1 2 3 2 1
5
7
3 3 1 2 3 2 1
1
1
6
5 5 5 4 4 4
8
1 1 7 7 6 6 1 1
12
3 3 4 4 4 4 3 3 3 2 5 1
4
0
6
4
10
*/
T3【转化思想(构造类)+状态压缩DP实现-->其实是暴力,不是正解,正解不会】给出n*m的01矩阵,每次可以进行行和列操作,求最后矩阵1个数的最少是多少。(n<=20,m<=1e5)
考场
首先受树上的数荼毒,一个点可以被变成0,那么所有经过它的操作次数一定有顺序限制-->奇偶性限制,但是发现这玩意没法弄,画了几个合法0000矩阵,瞎变换了一会儿,想到从最终合法状态倒推回初状态,那么相当于把一些单点看成“万能点”,就是是01都行,用于合法性弥补,于是行操作只有操作/不操作的选择,2^20枚举,列暴力修改,
暴力正解
神奇的思维:行操作+列操作=1点+0矩阵 转化 行操作+列操作+单点修改=合法矩阵 转化 列操作+单点修改=行完全一样的矩阵
i次单点=n-i次单点+1次列
点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念,极致,不念过往,不放当下
//freopen("2.in","w",stdout);
//freopen("1.in","w",stdout);
// auto T1=steady_clock::now();
// auto T2=steady_clock::now();
// auto T3=duration_cast<duration<double,ratio<1,1000> > >(T2-T1);
// if(T3.count()>900)break;
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define inf 2147483647
#define ll long long
#define rint register int
#define ull unsigned long long
#define lll __int128
#define ulll unsigned __int128
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
inline void wr(ll x)
{
if(x<0)
{
x=-x;putchar('-');
}
if(x>9)wr(x/10);
putchar(x%10+'0');
}
const int N=1e5+100;
int dp[22][(1<<20)+100];//dp[i][j]:操作i次单点,达到j状态的列个数
char s[N];
int sta[N];
int n,m;
int ans;
int main()
{
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
n=re(),m=re();
_f(i,1,n)
{
scanf("%s",s+1);
_f(j,1,m)
{
sta[j]=(sta[j]<<1)+s[j]-'0';
}
}
_f(i,1,m)dp[0][sta[i]]++;
_f(i,1,n)//单点修改的行对象
{
f_(j,n,1)//已经修改了多少
{
_f(k,0,(1<<n)-1)
{
dp[j][k]=dp[j][k]+dp[j-1][k^(1<<(i-1))];//只增加不减少?
}
}
}
ans=n*m;
_f(i,0,(1<<n)-1)
{
int res=0;
_f(j,0,n)res+=dp[j][i]*min(j,n-j);
ans=min(ans,res);
}
chu("%d",ans);
return 0;
}
/*
3 4
0110
0101
1110
*/
T4【计数类DP】给出一棵有点权的无根树,每次可以选择任意边的集合,使得边断开,但是要求任意次操作后所形成的联通块权值和相同。求方案数(n<=1e6,val<=1e9)
正解
考虑如果划分成k块,方案唯一【显然】,设
直接枚举k肯定是不行的,会T,考虑k的限制,
考虑最大的S/k就是
考虑统计答案,类似DUG路径统计,k的状态可以被自己凑出,也可以被任意k的因数凑出,从小到大枚举k,
具体实现到每个点枚举倍数上传就行,注意dp[k]的+1在dp[1]已经考虑就不用再+1了。
点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念,极致,不念过往,不放当下
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define inf 2147483647
#define ll long long
#define rint register int
#define ull unsigned long long
#define lll __int128
#define ulll unsigned __int128
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
inline void wr(ll x)
{
if(x<0)
{
x=-x;putchar('-');
}
if(x>9)wr(x/10);
putchar(x%10+'0');
}
const int N=1e6+100;
const ll mod=1e9+7;
int a[N],n,fa[N],tot;
ll s[N],dp[N];
int buc[N];
bool can[N];
inline ll gcd(ll x,ll y)
{
if(!y)return x;
return gcd(y,x%y);
}
int main()
{
//freopen("t4_.in","r",stdin);
//freopen("1.out","w",stdout);
//chu("%d",__gcd(656543083,65654308));
n=re();ll S=0;
_f(i,1,n)a[i]=s[i]=re(),S+=a[i];
_f(i,2,n)fa[i]=re();
f_(i,n,2)s[fa[i]]+=s[i];
_f(i,1,n)
{
ll chs=S/gcd(s[i],S);
if(chs<=n)buc[chs]++;
}
f_(i,n,1)
{
for(rint j=i+i;j<=n;j+=i)
{
buc[j]+=buc[i];//划分方案数
}
}
_f(i,1,n)if(buc[i]==i)can[i]=1;//初始状态,表示划分成i块的方案数
ll ans=0;
dp[1]=1;
_f(i,1,n)
{
if(!can[i])continue;
for(rint j=i+i;j<=n;j+=i)
{
dp[j]=(dp[j]+dp[i])%mod;
}
ans=(ans+dp[i])%mod;
}
chu("%lld",ans);
return 0;
}
/*
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】