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