机房模拟20181017

noip前冲刺阶段的机房模拟......随便搞搞

T1(签到题)

就随便看着1e9的数据..........什么O(n)、O(nlog(n)) 之类的数据就别想了吧.........

我们一看到xor,就应该可以马上反应过来其实可以拆位来做,因为一个数二进制下每一位对于答案的贡献是完全独立的,可以直接相加。

我们对于一位来说,肯定是只有0和1这两种位数,那么我们考虑何种情况下对答案是有贡献的。

首先,一对1和一对0是肯定没有贡献的,那么我们考虑一个1和一个0;

于是我们可以得到:假设这个数的这一位是1,那么肯定需要一个0才能使得这一个1对答案有贡献。

那么贡献是多少呢?

我们可以轻松得到0的个数*1的个数*(1<<i)*2

为什么要乘以2呢?

因为如果说1和0有贡献的话,0对1同样是有贡献的,所以我们计算两次就可以防止遗漏

接着我们考虑该如何统计每一位上[L,R]之间的1的个数

我们发现如果要从0开始数数,0、1、2、3、4、5……会出现以下情形

0000

0001

0010

0011

0100

0101

0110

0111

……

我们发现从低到高的话(从右向左),第一位的循环节长度是2(01 01 01)第二位是4(0011 0011 0011),第三位是8(00001111 00001111 00001111,而其中1、0个掺半。

如果我们直接根据L、R来考虑0、1的个数的话会非常的复杂,因为你不知道对于L来说到底处于当前循环节的位置。,R也同理 

但是如果说我们使用1~R间的个数减去1~L-1的个数的话,由于0肯定是一个任意一位的循环节的开始位置,那么我们只需要处理R就好了

R就随便取下模就OK了

代码

#include<cstdio>
#define OP "xor"
#define LL long long
const int MOD=1e9+7;
int main()
{
	std::freopen(OP".in","r",stdin);
	std::freopen(OP".out","w",stdout);
	int T;
	std::scanf("%d",&T);
	while(T--)
	{
		int l,r;
		std::scanf("%d%d",&l,&r);
		LL ans=0;
		l--;
		for(int i=0;(1<<i)<=r;i++)
		{
			int zl=(l+1)/(1<<i+1);
			int zr=(r+1)/(1<<i+1);
			int num1_l;
			int num1_r;
			int num0;
			int num1;
			if((l+1)%(1<<i+1)>(1<<i))
			{
				num1_l=(1<<i)*zl+(l+1)%(1<<i+1)-(1<<i);
			}
			else
			{
				num1_l=(1<<i)*zl;
			}
			if((r+1)%(1<<i+1)>(1<<i))
			{
				num1_r=(1<<i)*zr+(r+1)%(1<<i+1)-(1<<i);
			}
			else 
			{
				num1_r=(1<<i)*zr; 
			}
			num1=num1_r-num1_l;
			num0=(r-l)-num1;
			ans=(ans+2ll*num0*num1%MOD*(1<<i)%MOD)%MOD;
		}
		std::printf("%lld\n",ans);
	}
	return 0;
}
/*
2
1 2
0 1023

1
1 2
*/

T2(又一次超纲考博弈论........)

 

首先要搞清楚,这一道题虽然看上去和NIM游戏是如此的相似

但是并不一样.............

我们首先考虑简单的情况 (也就是部分分)

第一,当x、y、z很小的时候,我们可以考虑使用非常简单的博弈论基础内容,开一个三维数组来记录当前的x、y、z是必胜态或者是必败态,然后用必胜必败态的基础性质来转移(一个状态为必胜态当且仅当当前状态可以转移到一个必败态,一个状态为必败态当且仅当它能够转移到的所有状态均为必胜态)

这样的话时间复杂度为n^4

我们再来考虑只有两堆的情况(著名的威佐夫博弈)。

根据之前的做法打表,我们可以发现只有两堆的情况有一些特殊的性质,打个比方,我们打出只有两堆的情况时侯的前五项必败态,他们分别是

1 2 0

3 5 0

4 7 0

6 10 0

8 13 0

……

我们可以发现它们拥有的两个性质:

1、对于任意一个自然数,存在且仅存在一个自然数使得当前情况为必败态

2、对于任意一个必败态,其两堆石头的差值一定与任意必败态不同。

这两个性质都非常好证明

1、若对于自然数X,存在Y1,Y2使得其为必败态,我们假设Y1<Y2,则有:

X、Y2可以通过先手的操作使它变为X、Y1。

X、Y1为必败态,则后手必败,先手必胜,与X、Y2必败矛盾。

故对于任意一个自然数,存在且仅存在一个自然数使得当前情况为必败态。

2、对于自然数对(X1、Y1);(X2、Y2),两者均为必败态 ,且X1<X2,Y1<=Y2,且abs(x1-x2)==abs(y1-y2),则有

先手拿到X2、Y2状态

先手通过操作使其变为X1、Y1

X1、Y1必败,则后手必败,先手必胜,与X2、Y2必败矛盾

故对于任意一个必败态,其两堆石头的差值一定与任意必败态不同。

至此证毕

两者的证明其实真正依赖的定理都是博弈论的基础性质:一个必败态只能转移到必胜态,不可能转移到必败态,否则与它是必败态完全矛盾。

知道了这一个性质,当只有两堆棋子的时候我们就可以直接n^2for循环得到任意的值对应的必败态,如果另外的一个值是其对应的必败态的值则就输出No,否则输出Yes

至此,我们拥有了这道题目的70pts算法。

而实质意义上,两堆的情况稍作推理就可以转移至三堆的情况

对于已经确定的前两堆石子(a,b),存在且仅存在一个c使得这个三元组为必败态,其余均为必胜态

然后我们可以发现,对于同一个c,a、b之间的差值仍然不变。

于是我们定于f[a][b],其含义为当前两堆石头为a,b时所对应的唯一的使得当前状态成为必败态的第三堆石头

那么我们考虑从小到大枚举C

我们可以发现,对于C来说,由于从小到大枚举,那么f[a][b]=k且k<C的(A、B)是已经确定了的。

根据以上性质,我们可以得到:当存在f[a][b]=k且k<C的时候,有:f[a+c-k][b]!=c,f[a][b+c-k]!=c,f[a+c-k][b+c-k]!=c

所以我们记录一下这些特殊的必定不等的值,再判断一下a、b出现次数是否大于1以及他们的差值是否只出现一次,就可以得到a、b了

然后输入x、y、z,直接判断f[x][y]是否等于z,如果等于输出No(必败),否则输出Yes;

#include<cstdio>
#include<cstring>
#include<algorithm>
#define OP "stone"
const int MAXN=305;
int f[MAXN][MAXN];
int add[MAXN][MAXN];
int dif[MAXN];
int cnt[MAXN];
int Abs(int x)
{
	return x>=0?x:-x;
}
int main()
{
	std::freopen(OP".in","r",stdin);
	std::freopen(OP".out","w",stdout);
	std::memset(f,63,sizeof f);
	int tag=0;
	int T;
	std::scanf("%d",&T);
	for(int i=0;i<=300;i++)
	{
		tag++;
		for(int j=0;j<=300;j++)
		{
			for(int k=0;k<=300;k++)
			{
				if(f[j][k]<i)
				{
					int delta=i-f[j][k];
					if(j+delta<MAXN)
					{
						add[j+delta][k]=tag;
					}
					if(k+delta<MAXN)
					{
						add[j][k+delta]=tag;
					}
					if(std::max(j,k)+delta<MAXN )
					{
						add[j+delta][k+delta]=tag;
					}
				}
				else
				if(std::max(std::max(cnt[j],cnt[k]),dif[Abs(j-k)])<tag&&add[j][k]<tag)
				{
					f[j][k]=f[k][j]=i;
					cnt[j]=cnt[k]=tag;
					dif[Abs(j-k)]=tag;
				}
			}
		}
	}
	while(T--)
	{
		int x,y,z;
		std::scanf("%d%d%d",&x,&y,&z);
		f[x][y]==z?std::printf("No\n"):std::printf("Yes\n");
	}
	return 0;
}
/*
3
1 1 1
1 2 0
3 2 2 
*/

 

 如果你完美地看出了性质,这本来是不是很难的题目.....

但是性质贼TM难想,属于不知道就寸步难行(出题人原话)的那种

区间贡献的系数只有2、0、-2三种,根本不用考虑到底放在具体哪个区间,只需要考虑放在哪种区间即可.........

知道这个性质之后真的随随便便都能过............

#include<cstdio>
#include<cstring>
#include<algorithm>
#define OP "optimization"
const int MAXN=3e4+5;
const int INF=1e9+7;
const int KN=205;
int dp[MAXN][KN][4];
int a[MAXN];
int main()
{
	std::freopen(OP".in","r",stdin);
	std::freopen(OP".out","w",stdout); 
	for(int i=1;i<KN;i++)
	{
		for(int j=0;j<4;j++)
		{
			dp[0][i][j]=-INF;
		}
	}
	int n,k;
	std::scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		std::scanf ("%d",a+i);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=k;j++)
		{
			int flag=2-(j==1||j==k);
			dp[i][j][0]=std::max(dp[i-1][j][0],dp[i-1][j-1][3])-flag*a[i];
			dp[i][j][1]=std::max(dp[i][j][0],dp[i-1][j][1]);
			dp[i][j][2]=std::max(dp[i-1][j-1][1],dp[i-1][j][2])+flag*a[i];
			dp[i][j][3]=std::max(dp[i-1][j][3],dp[i][j][2]);
			if(flag-1)
			{
				dp[i][j][1]=std::max(dp[i][j][1],dp[i-1][j-1][1]);
				dp[i][j][3]=std::max(dp[i][j][3],dp[i-1][j-1][3]);
			}
		}
	}
	std::printf("%d\n",std::max(dp[n][k][1],dp[n][k][3]));
}
/* 
5 3
5 2 4 3 1
*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值