標籤:oid game 最佳化 類比 amp eve wap ++ 結束
先把題目講講,教訓什麼的放到講完再說
Problem 1. 密謀題目描述
丁國建造了 n 座雷射塔。
“ 當若干座雷射塔相連並形成環形線路,你就能獲得上神的力量。”這是丁國國王從大魔王那得知的訊息。
第二天,丁國國王便下令修建 \(m\) 條串連雷射塔的線路。
你作為甲國派來的間諜,無意間聽到了這個訊息,當即決定破壞這場密謀。每座雷射塔的座標為\((x_i,y_i)\),正在修建的線路\((u_i,v_i)\)表示串連編號為 \(u_i\) 和 \(v_i\) 的雷射塔,破壞該條線路的代價為兩座雷射塔的直線距離。求破壞這場密謀的最小代價。
輸入格式
第一行兩個整數 \(n, m\)
接下來 \(n\) 行, 每行兩個整數 \(x_i\), \(y_i\), 代表點 \(i\) 的座標
接下來 \(m\) 行, 每行兩個整數 \(u_i\), \(v_i\), 代表一條邊 \((u_i, v_i)\)
輸出格式
輸出一個實數代表答案, 當答案與標準答案之差的絕對值不超過 \(10^{-4}\) 時視為正確。
範例資料
input1
3 30 00 11 01 22 31 3
output1
1.000000000
input2
8 80 03 03 30 31 11 22 22 11 22 33 44 15 66 77 88 5
output2
4.000000000
資料規模與約定
對於前 \(20\%\)的資料:\(n,m \leq 20\)
對於前 \(50\%\)的資料:\(n,m \leq 1 000\)
對於 \(100\%\) 的資料:\(1 \leq n \leq 10000\) , \(1 \leq m \leq 50000\) , \(|x_i|,|y_i| \leq 10000\) , \(u_i ! = v_i\),不存在兩點重合的情況.
時間限制:1s
空間限制:512MB
題意很簡單,在平面直角座標系中給出 \(n\) 個點,並給出串連這 \(n\) 個點的 \(m\) 條邊,邊的權值為這兩個點的歐拉距離,斷掉某條邊的代價即為這條邊的權值。要求花費最小的代價使得剩下的邊無法構成環形線路很明確的一點,每個包含 \(k\) 個點的聯通塊必由 \(k-1\) 條邊串連起來,其餘的邊必須被斷掉。因為在 \(k-1\) 條邊的基礎上,若再多出來一條邊,那麼這條邊必定會使得兩點之間的路徑不唯一,也就是產生了環形線路,不符題意。在此基礎上,我們要使得斷邊的代價最小,也就是使留下的邊權和最大,那麼直接跑一遍最大產生樹,然後將未加入最大產生樹的邊權和輸出即可。
#include<cstdio>#include<iostream>#include<cmath>#include<algorithm>using namespace std;const int N=1e4+10;const int M=5e4+10;int n,m,x[N],y[N],b[N];double ans;struct edge{int a,b;double c;}ed[M];inline double dis(int a,int b){return sqrt(((double)x[a]-x[b])*((double)x[a]-x[b])+((double)y[a]-y[b])*((double)y[a]-y[b]));}bool cmp(edge x,edge y){return x.c>y.c;}inline int f(int x){if(b[x]!=x)b[x]=f(b[x]);return b[x];}int main(){ freopen("plan.in","r",stdin);freopen("plan.out","w",stdout); scanf("%d%d",&n,&m); for(register int i=1;i<=n;i++)scanf("%d%d",&x[i],&y[i]); for(register int i=1;i<=m;i++)scanf("%d%d",&ed[i].a,&ed[i].b),ed[i].c=dis(ed[i].a,ed[i].b); sort(ed+1,ed+m+1,cmp);for(register int i=1;i<=n;i++)b[i]=i; for(register int i=1;i<=m;i++){ int f1=f(ed[i].a),f2=f(ed[i].b);if(f1==f2){ans+=ed[i].c;continue;}b[f2]=f1;} printf("%.9lf\n",ans); fclose(stdin);fclose(stdout); return 0;}
Problem 2. 對決題目描述
丁國國王的密謀被破壞了。
大魔王很生氣,並且他知道是你壞了他的好事,於是他找來你。
“跟我玩個遊戲,你贏了,我就放你走。”
遊戲是這樣的:你和大魔王在一個規模為 \(1 * n\) 的棋盤中下棋,第一個人可以下在第\(1\) 到 \(m\) 中的任意一個位置。接下來每一個人可以下在第 \(i + 1\) 到 \(i + m\) 的任意一個位置,其中 \(i\) 為上一個人下棋的位置。每個格子裡有一個數,如果一個人下棋在格子 \(i\),會得到 \(a[i]\)的分值。當不能繼續操作時,遊戲結束。
大魔王允許你編個程式計算一下,當你和大魔王都採取 最優策略時, 你的得分減去大魔王的得分。
你們決定通過擲硬幣來決定先後手,因此你要把你先後手的兩種情況都考慮一下。
輸入格式
第一行:兩個正整數 \(n\) 和 \(m\) , 用空格隔開。
第二行:\(n\) 個數,表示棋盤上的數字。
輸出格式
兩行,每行各一個數, 第一個數為你先手時的答案, 第二個數為你後手時的答案。
範例資料
input1
1 31
output1
1-1
input2
2 1002 2
output2
2-2
資料規模與約定
對於前 \(30\%\)的資料:\(n \leq 15\)
對於前 \(60\%\)的資料:\(m \leq 100\)
對於 \(100\%\) 的資料:\(1 \leq n,m \leq 100000\) ,a 數組中的數保證在 int 範圍內。
時間限制:1s
空間限制:512MB
當時在考場裡看到這道題是懵逼的:What?這不是九省聯考一雙木棋?這麼難的博弈論題我還能做?於是胡亂搞一陣,如願以償的拿到了10分。好吧,其實冷靜分析一下也不算是太難:首先我們明確一下,這題是動歸題,那麼顯然可以設 \(dp[i][0/1]\),當第二維為 \(0\) 時,表示我在選,反之則表示大魔王在選,\(i\) 表示正在選的區間為 \([i,min(i+m-1,n)]\)。其次,要注意的是此題不能正序DP,因為當前點的最優決策一定是由後續狀態的決策轉移而來,類比於絕大多數機率期望DP,如果正序DP則無法知曉其後續狀態的決策了,所以選擇倒序DP那麼有DP方程:
\[dp[i][0]=max\{dp[j+1][1]+a[j]|i\leq j\leq min(i+m-1,n)\}\]
與
\[dp[i][1]=min\{dp[j+1][0]-a[j]|i\leq j\leq min(i+m-1,n)\}\]
其實還有另一種更方便的設法,只是我一時沒反應過來沒想到:把之前的設法中的第二維刪去,那麼就有新的DP方程:
\[dp[i]=max\{-dp[j+1]+a[j]|i\leq j\leq min(i+m-1,n)\}\]
假設上家為A,當前這家為B,那麼 \(dp[j+1]\) 是A相對於B的差值,當我們開始處理B時,就要對 \(dp[j+1]\) 取個負號表示B相對於A的差值,由於我們需要最大化B相對於A的差值,所以對該式取max即可但由於這樣轉移的複雜度是 \(O(nm)\) 的,所以使用單調隊列最佳化掉 \(m\),將複雜度降為線性 \(O(n)\),通過本題代碼是上面一種較煩的方法
#include<cstdio>#include<iostream>using namespace std;typedef long long ll;const int N=1e5+10;const int INF=1e9;int n,m;ll a[N],dp[N][2],q[N],p[N],head,tail,h,t;int main(){ freopen("game.in","r",stdin); freopen("game.out","w",stdout); scanf("%d%d",&n,&m); for(register int i=1;i<=n;i++)scanf("%lld",&a[i]); q[head=tail=1]=n;p[h=t=1]=n; for(register int i=n;i>=1;i--){ while(head<=tail&&dp[q[tail]+1][1]+a[q[tail]]<=dp[i+1][1]+a[i])tail--; q[++tail]=i; while(h<=t&&dp[p[t]+1][0]-a[p[t]]>=dp[i+1][0]-a[i])t--; p[++t]=i; while(head<=tail&&q[head]>=i+m)head++; dp[i][0]=dp[q[head]+1][1]+a[q[head]]; while(h<=t&&p[h]>=i+m)h++; dp[i][1]=dp[p[h]+1][0]-a[p[h]]; } printf("%lld\n%lld\n",dp[1][0],dp[1][1]); fclose(stdin);fclose(stdout); return 0;}
Problem 3. 尋路題目描述
至於丁國國王的下場……
大魔王一氣之下把他扔進了迷霧森林。迷霧森林可以看作一棵有 n 個節點的樹,根節點為 \(1\),但由於“Birds are everywhere.”,他得到了種種不一樣的訊息,每份訊息中都會告訴他有兩棵子樹是禁忌之地,於是他向你求助了。
他給出了 \(q\) 個形如x y的詢問,表示他不能走到 \(x\) 和 \(y\) 的子樹中,由於走的路徑越長他找到出口的機率越大並且他 只能走一條不經過重複節點的路徑,現在他想知道對於每組詢問他能走的最長路徑是多少,如果沒有,輸出零。
你剛剛在對決中打敗了大魔王,心情愉悅,決定幫丁國國王一把。
當然,出於本國利益,你也可以不幫他。
輸入格式
第一行兩個正整數 \(n\) 和 \(q\)(\(1 \leq n,q \leq 100000\))
第二到第 n 行每行兩個整數 \(u,v\) 表示 \(u\) 和 \(v\) 之間有一條邊串連,邊的長度為 \(1\)。
接下來 \(q\) 行每行兩個 \(x,y\) 表示一組詢問,意義如題目描述。
輸出格式
q 行,輸出見題目描述
範例資料
input
5 21 33 23 42 52 45 4
outpu
12
詢問 \(1\) 中 \(2\) 和 \(4\) 的子樹不能走,最長路徑為\((1,3)\)長度為 \(1\)
詢問 \(2\) 中 \(5\) 和 \(4\) 的子樹不能走,最長路徑為\((1,3,2)\)長度為 \(2\)
資料規模與約定
對於前 \(20\%\)的資料:\(1 \leq n,q \leq 100\)
對於前 \(50\%\)的資料:\(1 \leq n,q \leq 2000\)
對於 \(100\%\) 的資料:\(1 \leq n \leq 100000\) , \(1 \leq m \leq 50000\)
時間限制:1s
空間限制:512MB
做這題前,得先瞭解一個關於樹的直徑的性質:即將一棵樹切成兩部分,那麼原來那棵樹的直徑一定在這兩部分的直徑的四個端點之間有了這個性質,我們便可以把原樹中任意兩棵樹的直徑合并起來得到一個更大的直徑。因此,直接用線段樹維護DFS序某區間的直徑兩端點,查詢時合并一下即可關於查詢LCA,最好用歐拉序的方法實現 \(O(1)\) 查詢,因為本題merge操作常數較大,直接倍增LCA \(O(logn)\) 可能會被卡常
#include<cstdio>#include<iostream>using namespace std;const int N=1e5+10;int n,q,U,V,head[N],cnt,pre[N],las[N],p[N],dep[N<<1],depth[N],anc[N<<1][20],vk[N<<1],cl,cll,num[N],x,y;struct edge{int nxt,to;}ed[N<<1];struct data{int x,y;}seg[N<<2],ans;inline int read(){ int h=0;char ch=‘ ‘;while(ch<‘0‘||ch>‘9‘)ch=getchar(); while(ch>=‘0‘&&ch<=‘9‘)h=h*10+ch-‘0‘,ch=getchar();return h;}inline void addedge(int x,int y){ ed[++cnt].to=y;ed[cnt].nxt=head[x];head[x]=cnt; ed[++cnt].to=x;ed[cnt].nxt=head[y];head[y]=cnt;}inline void DFS(int u,int fa,int de){ dep[++cll]=de;vk[cll]=u;p[u]=cll;pre[u]=++cl;num[cl]=u;depth[u]=de; for(register int i=head[u];i;i=ed[i].nxt){ int v=ed[i].to;if(v==fa)continue; DFS(v,u,de+1);dep[++cll]=de;vk[cll]=u;p[u]=cll;} las[u]=cl;}inline void Preprocess(){ for(register int i=1;i<=cll;i++)anc[i][0]=i; for(register int j=1;(1<<j)<=cll;j++) for(register int i=1;i+(1<<j)-1<=cll;i++){ int a=anc[i][j-1],b=anc[i+(1<<(j-1))][j-1]; anc[i][j]=dep[a]<=dep[b]? a:b;}}inline int LCA(int x,int y){ int L=p[x],R=p[y],LOG=0;if(L>R)swap(L,R); while(1<<(LOG+1)<R-L+1)LOG++; int a=anc[L][LOG],b=anc[R-(1<<LOG)+1][LOG]; return dep[a]<=dep[b]? vk[a]:vk[b];}inline int dis(int x,int y){return depth[x]+depth[y]-(depth[LCA(x,y)]<<1);}inline data merge(data a,data b){ if(!a.x)return b;if(!b.x)return a; int val=dis(a.x,a.y),w;data c=a; if((w=dis(a.x,b.x))>val)val=w,c.x=a.x,c.y=b.x; if((w=dis(a.x,b.y))>val)val=w,c.x=a.x,c.y=b.y; if((w=dis(a.y,b.x))>val)val=w,c.x=a.y,c.y=b.x; if((w=dis(a.y,b.y))>val)val=w,c.x=a.y,c.y=b.y; if((w=dis(b.x,b.y))>val)val=w,c.x=b.x,c.y=b.y; return c;}inline void build(int o,int L,int R){ if(L==R){seg[o]=(data){num[L],num[L]};return;} int M=(L+R)>>1,lc=o<<1,rc=(o<<1)+1; build(lc,L,M);build(rc,M+1,R); seg[o]=merge(seg[lc],seg[rc]);}inline data query(int o,int L,int R,int l,int r){ if(l<=L&&R<=r)return seg[o]; int M=(L+R)>>1,lc=o<<1,rc=(o<<1)+1;data c=(data){0,0}; if(l<=M)c=merge(c,query(lc,L,M,l,r));if(r>M)c=merge(c,query(rc,M+1,R,l,r)); return c;}int main(){ freopen("find.in","r",stdin);freopen("find.out","w",stdout); n=read();q=read(); for(register int i=1;i<n;i++) U=read(),V=read(),addedge(U,V); DFS(1,0,1);Preprocess();build(1,1,n); while(q--){ x=read();y=read();ans=(data){0,0}; if(x==1||y==1){printf("0\n");continue;} int l1=pre[x],r1=las[x],l2=pre[y],r2=las[y]; if(l1>l2)swap(l1,l2),swap(r1,r2); if(l1<=l2&&r2<=r1){ if(1<=l1-1)ans=merge(ans,query(1,1,n,1,l1-1)); if(r1+1<=n)ans=merge(ans,query(1,1,n,r1+1,n));} else{ if(1<=l1-1)ans=merge(ans,query(1,1,n,1,l1-1)); if(r1+1<=l2-1)ans=merge(ans,query(1,1,n,r1+1,l2-1)); if(r2+1<=n)ans=merge(ans,query(1,1,n,r2+1,n));} printf("%d\n",dis(ans.x,ans.y)); } fclose(stdin);fclose(stdout); return 0;}
總結本次考試是目前6次類比賽中考的最差的一次,第一題數組沒開夠,第二題以為是博弈論不敢想太多,於是便以 \(120\) 分的好成績掉到了機房 \(rank6\),被一群神仙教育。。。所以,以後需要注意的是:1.數組要記得檢查是否開足2.有些類似博弈論的“最優策略”題動態規劃有時候也同樣可做3.注意分析題目性質4.考場策略和狀態很重要,記得及時調整
「20181024類比」Solution