這幾天充分感覺了被博弈論支配的恐懼……php
首先是參考資料:html
學長的一些課件……就不放了node
而後朱全民老師的課件ios
翻硬幣遊戲ide
樹上刪邊遊戲函數
還有這個dalao的模型總結挺好的,種類也很多……http://www.javashuo.com/article/p-zinqyvlq-bd.htmlui
而後……具體模型sg函數sj定理以及各類詭異的遊戲請看上面的參考資料……我只是瞎扯一些作題的感想spa
首先學了下極大極小搜索以及配套的alpha-beta剪枝.net
雖然……並非我當時作的那道題的正解……3d
可是這種思想很好,尤爲是剪枝的時候
咱們在搜的時候保留以前祖先節點的上界下界,若是不可能更新最優解了直接跳出。
大概貼的代碼……那個原題是在棧裏面取東西因此代碼打成這樣了
1 int f[N][N<<1][2]; 2 inline int min(int a,int b){return a<b?a:b;} 3 inline int max(int a,int b){return a>b?a:b;} 4 inline int searching(int player,int layer,int limit,int down,int up) 5 { 6 if(layer>n)return 0; 7 if(f[layer][limit][player]!=-1)return f[layer][limit][player]; 8 register int i,lim=min(layer+limit-1,n),v; 9 for(i=layer;i<=lim;++i) 10 { 11 v=searching(player^1,i+1,(i-layer+1)<<1,down,up); 12 if(!player)down=max(down,v); 13 else up=min(up,v); 14 if(down>up)break; 15 } 16 return f[layer][limit][player]=(player?up:down); 17 }
而後咱們來講說那道題的正解:博弈DP
博弈DP在轉移的時候利用了一個東西:因爲兩我的都是絕頂聰明的,因此面臨一樣狀態時決策必定會是相同的
而後對於本題的轉移方程,因爲對手必定會取最有利的局面,所以咱們轉移的時候不能取max而要取min……
看一下代碼:
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 #define N 2010 5 inline int min(int a,int b){return a<b?a:b;} 6 inline int max(int a,int b){return a>b?a:b;} 7 int f[N][N],n,val[N]; 8 int main() 9 { 10 // freopen("Ark.in","r",stdin); 11 register int i,j;scanf("%d",&n); 12 for(i=1;i<=n;++i)scanf("%d",&val[i]); 13 for(i=n;i;--i)val[i]+=val[i+1]; 14 for(i=1;i<=n;++i) 15 for(j=1;j<=n;++j) 16 { 17 f[i][j]=f[i][j-1]; 18 if(i-2*j+1>=0)f[i][j]=max(f[i][j],val[n-i+1]-f[i-2*j+1][2*j-1]); 19 if(i-2*j>=0)f[i][j]=max(f[i][j],val[n-i+1]-f[i-2*j][2*j]); 20 } 21 printf("%d\n",f[n][1]); 22 }
接着下一題……
這題以及後面的某些題大概是對每一個下標的元素計算sg函數值,而後異或
這多是一種解題技巧……
對於本題,對於下標i,咱們考慮全部在i操做以後影響到的狀態的mex值,
即咱們找出全部操做以後受影響的元素,把他們異或起來做爲狀態,而後再取mex
由於sg函數計算就是對到達的狀態取mex嘛……
代碼:
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 #define N 25 5 int n,sg[N]; 6 inline int read() 7 { 8 int x=0;register char c=getchar(); 9 while(c<'0'||c>'9')c=getchar(); 10 while(c>='0'&&c<='9')x=10*x+(c^48),c=getchar(); 11 return x; 12 } 13 bool vis[40]; 14 inline int get(int id) 15 { 16 register int i,j; 17 memset(vis,0,sizeof(vis)); 18 for(i=id+1;i<n;++i) 19 for(j=id+1;j<n;++j)vis[sg[i]^sg[j]]=1; 20 for(i=0;i<=n*2;++i)if(!vis[i])return i; 21 return n; 22 } 23 int main() 24 { 25 // freopen("Ark.in","r",stdin); 26 register int i,j,k,ans,cnt,t=read(); 27 while(t--) 28 { 29 n=read(),ans=cnt=0; 30 for(i=n-1;~i;--i)sg[i]=get(i); 31 for(i=0;i<n;++i)if((read()&1))ans^=sg[i]; 32 for(i=0;i<n;++i) 33 for(j=i+1;j<n;++j) 34 for(k=j;k<n;++k) 35 if(!(ans^sg[i]^sg[j]^sg[k])) 36 { 37 ++cnt; 38 if(cnt==1)printf("%d %d %d\n",i,j,k); 39 } 40 if(!cnt)puts("-1 -1 -1"); 41 printf("%d\n",cnt); 42 } 43 44 }
而後再來一道也是sg和下標有關的題目……
咱們認爲……這樣的翻硬幣相似物遊戲中每一個白點是獨立的
也就是說咱們把每一個下標的sg值都異或起來而後判斷便可
至於怎麼算……這道題咱們使用除法分塊來作
因爲後面n/2長度的sg值相等,那麼他們前面一段的sg值也相等,這樣推到前面去,在同一個除法分塊塊裏面的下標sg值都相等
因此咱們就能夠用根本跑不滿的$O(n)$來打這道題了!
代碼:
1 #include <cstdio> 2 #include <algorithm> 3 #include <cstring> 4 #include <cmath> 5 using namespace std; 6 #define N 66000 7 bool vis[1010]; 8 int len,hd[N],tot,cnt,l[N],r[N],sg[N],f[N]; 9 inline void init(int n) 10 { 11 register int i,j,cur,val,last; 12 for(i=1;i<=n;i=last+1) 13 hd[++tot]=last=n/(n/i); 14 r[0]=n,cnt=0; 15 for(i=tot;i;--i) 16 { 17 val=0,cur=cnt; 18 for(last=j=2;j<=n/hd[i];j=last+1) 19 { 20 while(cur&&j*hd[i]>r[cur])--cur; 21 last=r[cur]/hd[i]; 22 vis[val^f[cur]]=1; 23 if((last-j+1)&1)val^=f[cur]; 24 vis[val]=1; 25 } 26 for(j=1;vis[j];++j); 27 sg[i]=f[++cnt]=j; 28 if(cnt>1&&f[cnt]==f[cnt-1])l[--cnt]=hd[i-1]+1; 29 else l[cnt]=hd[i-1]+1,r[cnt]=hd[i]; 30 memset(vis,0,sizeof(vis)); 31 } 32 } 33 inline int gsg(int pos) 34 {return sg[upper_bound(hd+1,hd+tot+1,pos)-hd-1];} 35 int main() 36 { 37 // freopen("Ark.in","r",stdin); 38 register int i,j,t,n,a,ans; 39 scanf("%d%d",&len,&t); 40 init(len); 41 while(t--) 42 { 43 scanf("%d",&n),ans=0; 44 for(i=1;i<=n;++i) 45 scanf("%d",&a),ans^=gsg(len/(len/a)); 46 puts(ans?"Yes":"No"); 47 } 48 }
而後……下面這道題和sg函數並無什麼關係……
而後呢……咱們發現照題意這樣操做,一次操做的後繼狀態就太!多!了!
因而咱們考慮手玩小樣例可能這也是解題技巧吧2333
總之結論是」原石子能夠被分紅徹底相同的2堆「的時候先手必敗
至於具體的證實……
咱們採起什麼操做,咱們對手就利用和咱們取的那個石子堆對稱的那堆進行操做就好了,徹底模仿便可。
而後若是不徹底相同,咱們能夠用最大的那堆去把其餘的補齊,使得他們能成爲徹底相同的2組,而後把最大的扔到跟最小的相等
這時候咱們對手就必敗了……
而後代碼也很簡單啦……
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 char B[1<<15],*S=B,*T=B; 6 #define getc (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++) 7 inline int read() 8 { 9 int x=0;register char c=getc; 10 while(c<'0'||c>'9')c=getc; 11 while(c>='0'&&c<='9')x=10*x+(c^48),c=getc; 12 return x; 13 } 14 int a[100010]; 15 int main() 16 { 17 // freopen("Ark.in","r",stdin); 18 register int n,i,j; 19 for(n=read(),i=1;i<=n;++i)a[i]=read(); 20 for(sort(a+1,a+n+1),i=1;i<=n;++i) 21 if(a[i]!=a[i+1]){puts("first player");return 0;} 22 puts("second player");return 0; 23 }
而後作了個階梯博弈……彷佛頗有理有據的證實,你們想看能夠去上面的參考資料找。
不過這題好毒啊,居然要打ETT
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <iostream> 5 #include <algorithm> 6 using namespace std; 7 char B[1<<15],*S=B,*T=B; 8 #define getc (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++) 9 inline int read() 10 { 11 int x=0;register char c=getc; 12 while(c<'0'||c>'9')c=getc; 13 while(c>='0'&&c<='9')x=10*x+(c^48),c=getc; 14 return x; 15 } 16 #define N 100010 17 int n,lim,e,adj[N],fa[N],deep[N],val[N]; 18 struct edge{int zhong,next;}s[N]; 19 inline void add(int qi,int zhong) 20 {s[++e].zhong=zhong;s[e].next=adj[qi];adj[qi]=e;} 21 struct node 22 { 23 node *ch[2],*f; 24 int val,val0,val1,deep; 25 node(){val0=val1=0;} 26 inline void update() 27 { 28 val0=ch[0]->val0^ch[1]->val0; 29 val1=ch[0]->val1^ch[1]->val1; 30 if(deep&1)val0^=val;else val1^=val; 31 } 32 }*null,*root,mem[N<<1],*l[N],*r[N],*sta[N<<1],*tp; 33 inline void init() 34 { 35 null=new node(); 36 null->ch[0]=null->ch[1]=null->f=null; 37 null->val=null->val0=null->val1=0; 38 } 39 int top,tot; 40 inline bool isroot(node *o){return o->f==tp;} 41 inline int son(node *o){return o->f->ch[1]==o;} 42 inline node* newnode(int val,node *f,int dp) 43 { 44 node *o=mem+(tot++); 45 o->ch[0]=o->ch[1]=null,o->f=f; 46 o->deep=dp,o->val=val; 47 if(dp&1)o->val0=val;else o->val1=val; 48 return o; 49 } 50 inline void dfs(int rt) 51 { 52 deep[rt]=deep[fa[rt]]+1; 53 l[rt]=newnode(val[rt],null,deep[rt]);sta[++top]=l[rt]; 54 for(int i=adj[rt];i;i=s[i].next)dfs(s[i].zhong); 55 r[rt]=newnode(0,null,deep[rt]);sta[++top]=r[rt]; 56 } 57 inline void rotate(node *o) 58 { 59 node *fa=o->f,*grand=fa->f; 60 int k=son(o),kk=son(fa); 61 fa->ch[k]=o->ch[k^1]; 62 if(o->ch[k^1]!=null)o->ch[k^1]->f=fa; 63 o->ch[k^1]=fa,fa->f=o,o->f=grand; 64 if(grand!=null)grand->ch[kk]=o; 65 fa->update(),o->update(); 66 } 67 inline void splay(node *o,node *towards) 68 { 69 for(tp=towards;!isroot(o);rotate(o)) 70 if(!isroot(o->f))rotate(son(o)==son(o->f)?o->f:o); 71 } 72 inline node* getpre(node *o) 73 { 74 splay(o,null),o=o->ch[0]; 75 while(o->ch[1]!=null)o=o->ch[1]; 76 return o; 77 } 78 inline node* getback(node *o) 79 { 80 splay(o,null),o=o->ch[1]; 81 while(o->ch[0]!=null)o=o->ch[0]; 82 return o; 83 } 84 inline node* get_range(node* a,node* b) 85 { 86 node *o=getpre(a),*oo=getback(b); 87 splay(o,null),splay(oo,o);return oo->ch[0]; 88 } 89 inline node* build(int l,int r) 90 { 91 if(l>r)return null; 92 register int mi=l+r>>1; 93 sta[mi]->ch[0]=build(l,mi-1),sta[mi]->ch[1]=build(mi+1,r); 94 if(sta[mi]->ch[0]!=null)sta[mi]->ch[0]->f=sta[mi]; 95 if(sta[mi]->ch[1]!=null)sta[mi]->ch[1]->f=sta[mi]; 96 sta[mi]->update();return sta[mi]; 97 } 98 inline int query(int id) 99 { 100 int ret; 101 node *o=get_range(l[id],r[id]); 102 if(deep[id]&1)ret=o->val1!=0; 103 else ret=o->val0!=0; 104 puts(ret?"MeiZ":"GTY"); 105 return ret; 106 } 107 inline void update(int id,int v) 108 { 109 splay(l[id],null); 110 l[id]->val=val[id]=v; 111 l[id]->update(); 112 } 113 inline void insert(int f,int id) 114 { 115 node *o=getpre(r[f]); 116 splay(r[f],null),splay(o,r[f]); 117 l[id]=newnode(val[id],null,deep[id]); 118 r[id]=newnode(0,null,deep[id]); 119 l[id]->ch[1]=r[id],r[id]->f=l[id],l[id]->update(); 120 o->ch[1]=l[id];l[id]->f=o;o->update(); 121 } 122 int main() 123 { 124 // freopen("Ark.in","r",stdin); 125 register int i,a,b,c,m,cnt=0,opt; 126 n=read(),lim=read(); 127 for(i=1;i<=n;++i)val[i]=read()%(lim+1); 128 for(i=1;i<n;++i)a=read(),b=read(),fa[b]=a,add(a,b); 129 init(); 130 l[0]=newnode(0,null,0),sta[++top]=l[0]; 131 for(i=1;i<=n;++i)if(!fa[i]){dfs(i);break;} 132 r[0]=newnode(0,null,0),sta[++top]=r[0]; 133 root=build(1,top),m=read(); 134 while(m--) 135 { 136 opt=read(),a=read()^cnt; 137 switch(opt) 138 { 139 case 1:cnt+=query(a);break; 140 case 2:b=read()^cnt;update(a,b%(lim+1));break; 141 case 3: 142 b=read()^cnt;val[b]=read()^cnt; 143 fa[b]=a;deep[b]=deep[a]+1; 144 val[b]%=(lim+1);insert(a,b);break; 145 } 146 } 147 }
而後是樹上刪邊遊戲……參考資料裏面有
在會了那個結論以後仍是比較裸的,dp一發求個方案數再轉機率就好了
有這樣一句話比較好「在博弈論中凡是等價的狀態都是能夠相互替換的」,正因如此咱們能夠把以前那一堆樹枝替換成一條「竹子」
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 #define N 110 5 #define db double 6 #define K 128 7 int n; 8 db f[N][K<<1],g[N][K<<1]; 9 bool vis[N][K<<1]; 10 inline void init() 11 { 12 register int i,j,u,v,cnt=1; 13 vis[1][0]=1;g[1][0]=1;db tmp; 14 for(i=2;i<=100;++i) 15 { 16 for(u=0;u<i-1;++u) 17 if(vis[i-1][u]) 18 vis[i][u+1]=1,g[i][u+1]+=2*g[i-1][u]; 19 for(j=1;j<i;++j) 20 for(u=0;u<j;++u)if(vis[j][u]) 21 for(v=0;v<i-j-1;++v)if(vis[i-j-1][v]) 22 vis[i][(u+1)^(v+1)]=1,g[i][(u+1)^(v+1)]+=g[j][u]*g[i-j-1][v]; 23 } 24 for(i=2;i<=100;++i) 25 { 26 for(tmp=0,j=0;j<i;++j)tmp+=g[i][j]; 27 for(j=0;j<i;++j)g[i][j]/=tmp; 28 } 29 } 30 int main() 31 { 32 // freopen("Ark.in","r",stdin); 33 register int i,j,k,a; 34 scanf("%d",&n),init(),f[0][0]=1; 35 for(i=1;i<=n;++i) 36 for(scanf("%d",&a),j=0;j<K;++j) 37 for(k=0;k<a;++k) 38 f[i][j^k]+=f[i-1][j]*g[a][k]; 39 printf("%.6f\n",1-f[n][0]); 40 41 }
以及Nimk遊戲,固然上面的資料裏面也是有的
這也很好的解釋了爲何普通的min遊戲是異或:
異或以後每一位的值是本來1的個數x%(1+1)以後的值
也就是說異或啦,有偶數個1是0,奇數個1是1
上面那個方法總結的博客裏介紹了證實……
彷佛我這樣扔資料不太負責任啊2333
咱們能夠和以前那道也是棋子游戲的模型參考一下……
而後咱們就發現這倆題都是那樣棋子轉nim的模型,不過這題是nimk而已
打就行了……拿組合數Dp一下方案數
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 #define N 10010 5 #define N1 10000 6 #define K 110 7 #define mod 1000000007 8 #define LL long long 9 int bin[25],n,k,d; 10 LL f[17][N],fac[N],inv[N]; 11 inline LL C(int a,int b){return a<b?0:(fac[a]*inv[b]%mod*inv[a-b]%mod);} 12 int main() 13 { 14 // freopen("Ark.in","r",stdin); 15 register int i,j,x,ge; 16 for(bin[0]=i=1;i<=20;++i)bin[i]=bin[i-1]<<1; 17 scanf("%d%d%d",&n,&k,&d); 18 for(fac[0]=fac[1]=1,i=2;i<=N1;++i)fac[i]=fac[i-1]*i%mod; 19 for(inv[0]=inv[1]=1,i=2;i<=N1;++i)inv[i]=(mod-mod/i)*inv[mod%i]%mod; 20 for(i=3;i<=N1;++i)inv[i]=inv[i]*inv[i-1]%mod; 21 LL ans=C(n,k); 22 f[0][0]=1; 23 for(i=0;i<=15;++i) 24 for(j=0;j<=n-k;++j) 25 { 26 f[i+1][j]=(f[i+1][j]+f[i][j])%mod; 27 for(x=1,ge=(d+1)*bin[i];x*(d+1)<=k/2&&j+ge<=n-k;++x,ge+=bin[i]*(d+1)) 28 f[i+1][j+ge]=(f[i+1][j+ge]+f[i][j]*C(k/2,x*(d+1)))%mod; 29 } 30 for(j=0;j<=n-k;++j) 31 ans-=f[16][j]*C(n-j-k/2,k/2)%mod; 32 printf("%lld\n",(ans%mod+mod)%mod ); 33 }
而後最後來一道二分圖博弈……
關於二分圖博弈,一個特色就是咱們每一個點只能走一次
常常和棋盤黑白染色轉二分圖搞在一塊兒
必勝和必敗經常與二分圖的交錯軌和匹配邊聯繫在一塊兒:
nim遊戲中咱們對於2堆同樣的石子會模仿對手的操做,而後對於二分圖類博弈咱們從對手的點出發走匹配邊
也是在模仿對手啦……只有對方能走,咱們就能走他那個點出去的匹配邊,因此是資瓷的
而後常常要考慮是最大匹配的」必須點「仍是」非必須點「
前者能夠經過從最大匹配的非匹配點dfs獲得,後者能夠刪掉這個點,看看本身的匹配點還能不能找到匹配
而後當時我搞的時候看了這篇總結,寫的很不錯……
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 #define N 45 5 #define G 1610 6 #define K 1010 7 int n,m,opt[K<<1],k,ans[K],top; 8 int stcol,id[N][N],v[N][N],col[N][N],cnt; 9 char str[N]; 10 int e,adj[G],match[G]; 11 struct edge{int zhong,next;}s[G<<2]; 12 inline void add(int qi,int zhong) 13 {s[++e].zhong=zhong;s[e].next=adj[qi];adj[qi]=e;} 14 int vis[G],T; 15 bool del[G]; 16 inline bool find(int rt) 17 { 18 if(del[rt])return false; 19 register int i,u,x; 20 for(i=adj[rt];i;i=s[i].next) 21 if(vis[u=s[i].zhong]!=T) 22 { 23 vis[u]=T; 24 if(del[u])continue; 25 if(!match[u]||find(match[u])) 26 {match[rt]=u,match[u]=rt;return true;} 27 } 28 return false; 29 } 30 inline bool judge(int rt) 31 { 32 del[rt]=1; 33 if(!match[rt])return 0; 34 int tofind=match[rt]; 35 ++T,match[rt]=match[tofind]=0; 36 return find(tofind)==0; 37 } 38 int main() 39 { 40 // freopen("Ark.in","r",stdin); 41 register int i,j,a,b; 42 scanf("%d%d",&n,&m); 43 for(i=1;i<=n;++i) 44 for(j=1;j<=m;++j)col[i][j]=((i+j)&1); 45 for(i=1;i<=n;++i) 46 for(scanf("%s",str+1),j=1;j<=m;++j) 47 { 48 if(str[j]=='.')a=i,b=j,stcol=col[i][j]; 49 v[i][j]=(str[j]!='O'); 50 } 51 for(i=1;i<=n;++i) 52 for(j=1;j<=m;++j) 53 if((v[i][j]&&col[i][j]==stcol)||(v[i][j]==0&&col[i][j]!=stcol)) 54 id[i][j]=++cnt; 55 for(i=1;i<=n;++i) 56 for(j=1;j<=m;++j) 57 if(id[i][j]) 58 { 59 if(id[i-1][j])add(id[i][j],id[i-1][j]); 60 if(id[i+1][j])add(id[i][j],id[i+1][j]); 61 if(id[i][j-1])add(id[i][j],id[i][j-1]); 62 if(id[i][j+1])add(id[i][j],id[i][j+1]); 63 } 64 for(i=1;i<=cnt;++i) 65 if(!match[i])++T,find(i); 66 scanf("%d",&k);k<<=1; 67 for(i=1;i<=k;++i) 68 opt[i]=judge(id[a][b]),scanf("%d%d",&a,&b); 69 for(i=1;i<=k;i+=2) 70 if(opt[i]&&opt[i+1])ans[++top]=((i+1)>>1); 71 printf("%d\n",top); 72 for(i=1;i<=top;++i)printf("%d\n",ans[i]); 73 } 74 //二分圖類的博弈論? 75 //一個格子只能被訪問一次
大概我如今學的東西就這些……博弈論真的是頗有趣的知識,一開始很怕這個……
可是如今發現這種博弈的思想頗有意思,雙方都要取最優策略,
而後要靈活的考慮sg函數的意義,模型的轉換,技巧的使用,固然還有背結論
但願你們學的開心……
啊累死了我要回去睡覺