1. 程式人生 > >機房模擬20181017

機房模擬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
*/