随笔 - 188  文章 - 0  评论 - 59  阅读 - 7707

2022NOIP联测8

T1【博弈论:公平游戏】给出n堆石子,A和B轮流操作,A每次可以选择>=1堆石子同时取走x个,B每次可以选择>=1堆石子同时取走y个,求最优决策下谁一定会赢。(n<=1e5)

当ai<x+y,问题变得非常简单,因为对于面临这个局面的选手op,如果存在ai<val[op]ai>val[!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]表示是否可以完全删除
dp[i]:cori
dp[i]=max(dp[j])(d[j+1][i1]==1cor[j]==cor[i])
ans=max(ans,dp[i])(d[i+1][n]==1)
O(n2)

点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念,极致,不念过往,不放当下
#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枚举,列暴力修改,O(2nmn)

暴力正解

神奇的思维:行操作+列操作=1点+0矩阵 转化 行操作+列操作+单点修改=合法矩阵 转化 列操作+单点修改=行完全一样的矩阵
dp[i][j]:ij
dp[i][j]=sigma(dp[i1][jS])(S)
ans=min(ans,dp[0 n][S]min(ni,i))
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块,方案唯一【显然】,设S=sigma(ai),w=S/k而且必须满足有k个节点子树大小是w的倍数(为什么要求子树就行?划分成的联通块肯定会包含根(子树外):因为对于倍数具有传递性:a,a+b是G的倍数,那么b也是,所以断开的包含根部分就默认进去了,此时sum_root就代表了那部分贡献)。
直接枚举k肯定是不行的,会T,考虑k的限制,(S/k))|S,(S/k)|si>S=ci(S/k),si=cj(S/k),(S/k)Ssi
考虑最大的S/k就是ko=gcd(S,si),最小的k就是S/gcd(S,si),也就是如果ko是合法的,那么它可以对ko2,ko3,ko*4...产生贡献,所以处理出每个节点可以对哪个k产生贡献(因为k的极小性,所以只计k就行),然后进行有限背包,如果“k”有k个节点可以凑出就是合法,即为f[k]=1
考虑统计答案,类似DUG路径统计,k的状态可以被自己凑出,也可以被任意k的因数凑出,从小到大枚举k,
dp[k]=sigma(dp[s])+1(s|k)
具体实现到每个点枚举倍数上传就行,注意dp[k]的+1在dp[1]已经考虑就不用再+1了。
O(nlog(n))

点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念,极致,不念过往,不放当下
#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;
}
/*
*/
posted on   HZOI-曹蓉  阅读(15)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示