標籤:關係 最大值 題目 init 不能 char 約束 stdin register
雅禮學習10.3
各題狀況T1
暴力修改+一維差分+二維差分
莫名其妙就沒了49分。。。
好像是數組開的不夠大?
T2
這。。。機率和期望,一會不會,連那個一分的部分分都沒有任何思路
T3
題目並沒有看太懂。。
寫了一個枚舉演算法,然後對某個一分的資料輸出顯然的結果
。。。
然後就只拿了1分
枚舉掛了,因為會錯了題目含義
題目及考場代碼T1
/* * 一個個修改肯定逾時。。 * q==0的直接輸出0 * 19分應該是暴力 * * 考慮對每次操作,計算一共修改了多少個位置 * 奇數個的話就讓當前答案異或這個數字 * 對於邊界單獨討論 * 但是問題在於。。。如果修改的位置剛好把當前答案所在位置變大了,就不應該是異或,而是+ * 真的不會維護啊 * 假裝這麼寫是正確的吧。。。 * * 每行差分 * 最後O(n^2)統計 */#include <cstdio>inline int read(){ int n=0,w=1;register char c=getchar(); while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar(); return n*w;}inline int min(int x,int y){return x<y?x:y;}const int N=1002;long long map[N][N],add[N][N],cut[N][N];int main(){ freopen("u.in","r",stdin); freopen("u.out","w",stdout); int n=read(),q=read(),r,c,s,l,x; long long ans=0; if(q==0) { printf("0"); goto E; } if(q<=400) { while(q--) { r=read(),c=read(),l=read(),s=read(); x=min(r+l-1,n); for(int i=r;i<=x;++i) for(int j=c;j<=min(i-r+c,n);++j) map[i][j]+=s; } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) ans^=map[i][j]; } else if(q<=2000) { while(q--) { r=read(),c=read(),l=read(),s=read(); x=min(r+l-1,n); for(int i=r;i<=x;++i) map[i][c]+=s,map[i][min(i-r+c,n)+1]+=-s; } for(int i=1;i<=n;++i) { x=0; for(int j=1;j<=n;++j) { x+=map[i][j]; printf("%d ",x); ans^=x; } puts(""); } } else { while(q--) { r=read(),c=read(),l=read(),s=read(); add[r][c]+=s,add[r+l][c]-=s; cut[r][c+1]+=s,cut[r+l][c+l+1]-=s; } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) { add[i][j]+=add[i-1][j]; cut[i][j]+=cut[i-1][j-1]; } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) { map[i][j]+=map[i][j-1]+add[i][j]-cut[i][j]; ans^=map[i][j]; } } /* else { while(q--) { r=read(),c=read(),l=read(),s=read(); x=l+1; if(r+l-1>n || c+l-1>n) {//想了半天沒想出來怎麼快速把多餘的部分刪掉。 x=0; for(int i=r;i<=min(r+l-1,n);++i) x+=min(i-r+c,n); } if(x&1)ans^=s; } }*/ printf("%lld",ans); E: fclose(stdin);fclose(stdout); return 0;}
T2
/* * 機率。。。 * 根本一會不會啊。。。 * 騙分走人 */#include <cstring>#include <cstdio>const int N=31;char s[N];int main(){ freopen("v.in","r",stdin); freopen("v.out","w",stdout); scanf("%d%d",&n,&k); scanf("%s",s); int x=0; for(int i=0;i<n;++i) if(s[i]=='W') ++x; if(k==0 || k==n) printf("%.10lf",(double)1.0*k); else printf("%.10lf",(double)x*1.0/2); fclose(stdin);fclose(stdout); return 0;}
T3
/* * 輸出0有1分。 */#include <cstdio>#include <vector>int n,step,cnt,k_2,z_2,ku;int l_note[100023];struct note{ int con,have,wash;};bool dis[100023];std::vector<note> tree[100023];std::vector<int> kuai[100023];void search(int num){ for(int space,i=0;i<l_note[num];i++) { space=tree[num][i].con; if(((tree[num][i].wash!=tree[num][i].have)||(tree[num][i].wash==2))&&dis[space]!=true) { tree[num][i].have=tree[num][i].wash; ++cnt; dis[num]=true; search(space); break; } }}bool vis[100023];void qk(int num){ vis[num]=true; for (int i=0;i<kuai[num].size();++i) { int space=kuai[num][i]; if(tree[num][i].wash==2)++k_2; if(vis[space]!=true) qk(space); }}int main (){ freopen("w.in","r",stdin); freopen("w.out","w",stdout); scanf("%d",&n); if(n<=1000) { for(int a,b,c,d,i=1;i<n;++i) { scanf("%d%d%d%d",&a,&b,&c,&d); tree[a].push_back((note){b,c,d}); ++l_note[a]; tree[b].push_back((note){a,c,d}); ++l_note[b]; if((c!=d)||(d==2)) { kuai[a].push_back(b); kuai[b].push_back(a); } if(d==2)++z_2; } for(int i=1;i<=n;++i) { for(int o=0;o<l_note[i];o++) { if((tree[i][o].wash!=tree[i][o].have)&&tree[i][o].wash!=2) { dis[tree[i][o].con]=true; search(i); break; } } } for(int i=1;i<=n;++i) if(vis[i]!=true&&kuai[i].size()!=0) { ++ku; qk(i); } step=ku-(z_2-k_2/2); printf("%d %d",step,cnt); } else printf("0 0"); fclose(stdin);fclose(stdout); return 0;}
正解T1
第一眼\(n\times q\)做法,\(3e8\)顯然不能過
那麼考慮對其進行最佳化:差分序列的訪問時不連續的,所以我們考慮把它變成連續的
就過了。。。
官方正解:
豎著做一次差分,斜著做一次差分,那麼一個三角形就可以確定出來了,而後\(n^2\)掃一遍求異或和
#include<bits/stdc++.h>using namespace std;typedef long long ll;const int maxn=1e3+10;int n,q;ll a[maxn][maxn],b[maxn][maxn],ans;inline void add_a(int x,int y,int v){ if(x<=n&&y<=n) a[x][y]+=v;}inline void add_b(int x,int y,int v){ if(x<=n&&y<=n) b[x][y]+=v;}int main(){ freopen("u.in","r",stdin); freopen("u.out","w",stdout); scanf("%d%d",&n,&q); while(q--){ int r,c,l,s; scanf("%d%d%d%d",&r,&c,&l,&s); add_a(r,c,s); add_a(r+l,c+l,-s); add_b(r+l,c,-s); add_b(r+l,c+l,s); } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j){ if(i>1) a[i][j]+=a[i-1][j-1]+a[i-1][j]-a[i-2][j-1]; else a[i][j]+=a[i-1][j-1]+a[i-1][j]; b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1]; ans^=a[i][j]+b[i][j]; } printf("%lld\n",ans); return 0;}
T2
可以得到一個\(O(2^n\times n)\)的狀壓\(DP\)的做法,記錄每個球是否還沒有被移除,然後按照最優策略期望移除白球數
事實上有很多重複狀態,也就是剩下的求的顏色序列相同時結果是一樣的
考慮將狀態記成剩下的顏色序列,長度較小的時候就直接用數組去存,較大的時候用\(map\)去存
狀態個數能得到一個上界是\(\sum_{i=0}^n\min\{2^i,{i\choose n}\}\)(事實上最大值為\(\sum_{i=1}^{n+1}Fib_i\)但是這一點也不\(noip\))
#include<bits/stdc++.h>using namespace std;const int maxn=30+5;int n,k;char s[maxn];namespace ${ const int xxx=24; double a[1<<xxx+1]; map<int,double> m[maxn]; inline void init(){ for(int i=0;i<1<<xxx+1;++i) a[i]=-1; } inline bool count(int bit,int len){ if(len<=xxx) return a[1<<len|bit]!=-1; else return m[len].count(bit); } inline double&find(int bit,int len){ if(len<=xxx) return a[1<<len|bit]; else return m[len][bit]; }}inline int erase(int bit,int k){ return bit&(1<<k)-1|bit>>1&-1<<k;}inline double max_(double a,double b){ return a>=b?a:b;}double dfs(int bit,int len){ if(len<=k) return 0; if($::count(bit,len)) return $::find(bit,len); double&res=$::find(bit,len); res=0; for(int i=0,j=len-1;i<=j;++i,--j) if(i<j) res+=max_(dfs(erase(bit,i),len-1)+(bit>>i&1),dfs(erase(bit,j),len-1)+(bit>>j&1))*2; else res+=dfs(erase(bit,i),len-1)+(bit>>i&1); return res/=len;}int main(){ freopen("v.in","r",stdin); freopen("v.out","w",stdout); $::init(); scanf("%d%d%s",&n,&k,s); k=n-k; int bit=0; for(int i=0;i<n;++i) bit|=(s[i]=='W')<<i; printf("%.10f\n",dfs(bit,n)); return 0;}
T3
如果最後翻轉的邊集是\(S\),最少運算元為\(\{V,S\}\)中奇數度數的點的一半,最小操作總長度是\(|S|\)
考慮樹形\(DP\),\(dp[i][0/1]\)記錄以\(i\)為根的子樹內,\(i\)與父親之間的邊是否翻轉,最少的奇數度數的點的個數,此時的最小總長度
#include<bits/stdc++.h>using namespace std;const int maxn=1e5+10;const pair<int,int> inf=make_pair(1e9,1e9);int n;vector<pair<int,int> > g[maxn];pair<int,int> dp[maxn][2];inline pair<int,int> operator+ (pair<int,int> a,pair<int,int> b){ return make_pair(a.first+b.first,a.second+b.second);}void dfs(int pos,int fa,int type){ pair<int,int> tmp0(0,0),tmp1(inf); for(int i=0,v;i<g[pos].size();++i) if((v=g[pos][i].first)!=fa){ dfs(v,pos,g[pos][i].second); pair<int,int> nxt0,nxt1; nxt0=min(tmp0+dp[v][0],tmp1+dp[v][1]); nxt1=min(tmp1+dp[v][0],tmp0+dp[v][1]); tmp0=nxt0;tmp1=nxt1; } if(type==0||type==2) dp[pos][0]=min(tmp0,make_pair(tmp1.first+1,tmp1.second)); else dp[pos][0]=inf; if(type==1||type==2) dp[pos][1]=min(make_pair(tmp0.first+1,tmp0.second+1),make_pair(tmp1.first,tmp1.second+1)); else dp[pos][1]=inf;}int main(){ freopen("w.in","r",stdin); freopen("w.out","w",stdout); scanf("%d",&n); for(int i=1,a,b,c,d;i<n;++i){ scanf("%d%d%d%d",&a,&b,&c,&d); if(d!=2) d=(c!=d); g[a].push_back(make_pair(b,d)); g[b].push_back(make_pair(a,d)); } dfs(1,0,0); printf("%d %d\n",dp[1][0].first/2,dp[1][0].second); return 0;}
下午講課:搜尋與剪枝
主要是題目只有題目
例一
有一個\([1,2^n]\)的排列\(A\{1,\cdots,2^n\}\)
可以執行的操作有\(n\)種,每種操作最多可以執行一次。第\(i\)種操作:將序列從左至右劃分為\(2^{n-i+1}\)段,每段恰好包括\(2^{i-1}\)個數,然後整體交換其中兩段。
求可以將數組\(A\)從小到大排序的不同的操作序列有多少個。
兩個操作序列不同,若且唯若操作個數不同,或者至少一個操作不同(種類不同或者操作位置不同)。
\(n\le 12\)
解:
首先,任意一個合法的操作序列,我們可以改變其順序,依然滿足條件。
那麼這一類的操作序列的貢獻,即為操作次數的階乘。
那麼,現在只考慮種類編號遞增的操作序列。第\(i\)種操作時,序列分成了大小為\(2^{i-1}\)的段,如果某個段不是遞增且連續的,那麼最後肯定不會滿足條件。所以,在這種操作考慮完後,每個大小為\(2^i\)的段應當遞增且連續。
當考慮第\(i\)種操作時:
- 如果不合條件的\(2^i\)大小的段超過\(2\)個,直接退出
- 如果不存在的話,直接繼續
- 如果只有\(1\)個,交換其包含的\(2^{i-1}\)的兩段,判斷是否可行
- 如果有\(2\)個就分別搜尋\(4\)種交換方案
例二
農夫約翰需要一些特定規格的木材(共\(n\)塊,長度不一定相同),可是他只剩下一些大規格的木板(共\(m\)塊,長度不一定相同)。不過約翰可以將這些木板切割成他所需要的規格。
求約翰最多能夠得到多少他所需要的木材。
\(n\le 1000,m\le 50,lenth\le 32767\)
解:
顯然可以二分答案。
判斷答案為\(k\)是否可行,顯然可以將木材排序,搜尋最短的k 塊木材是否
能得到。
然後剪枝:
- 從小到大搜尋所需的木材從哪塊木板得到,因為長的木材限制比較大
- 如果兩塊所需木材長度相同,後搜尋的那一塊的來源只需要從前者的來源開始枚舉
- 當一塊原料的長度比最短的需求還短,那麼直接丟棄,如果丟棄總量+所需總量\(\gt\)原料總量,剪掉
例三
給出一個數字\(S\),輸出所有約數和等於\(S\)的數
一共\(T\)次詢問
\(S\le 2e9,T\le 100\)
解:
\(n=\prod_i p_i^{a^i}\Rightarrow \sigma(n)=\prod_i\sum_{j=0}^{a_i}p_i^j\)
那麼我們可以通過枚舉\(p_i\)及其\(a_i\)來搜尋
若當前需要得到的\(S\)可以表示為為一個未搜尋過的質數與\(1\)的和,那麼之前的數與這個質數的乘積是一個合法答案
對於每個使得\((p+1)(p-1)\lt S\)的\(p\),枚舉可能的\(a_i\)進行遞迴
例四
一個\(n\times m\)的網格,\(k\)種顏色,部分格子已經塗了某種顏色,現在需要將其他格子也塗上顏色,使得從\((1,1)\)到\((n,m)\)的每條路徑(每次向下或向右走一格)都不會出現重複顏色。求方案數,對\(10^9+7\)模數。
\(n,m\le 1000,k\le 10\)
解:
顏色數應大於等於步數,\(n+m-1\le k\Rightarrow n+m\le k+1\le 11\),否則puts("0")。
然後搜每個位置的顏色,可以狀壓到每個位置的已經過的顏色。
可行性剪枝:未經過的顏色數小於剩餘步數,剪掉。
對稱性剪枝:如果塗上一個未出現過的顏色,塗哪一個都是等價的,那麼只需搜其中一個。
例五
有\(52\)張牌排成一排\(\{0,1\cdots 51\}\),然後可以把他們分成兩半,然後交叉著洗牌(一個確定的置換)。
洗完牌後,可能會出現一個錯誤,將一對相鄰的牌調換了位置,但是每次洗牌最多隻會犯一個錯誤。
現在給出牌最終的順序,求最少洗了多少次(保證不超過\(10\)),最少犯幾次錯誤。
解:
一個性質:確定了洗牌次數後,若真實的最終排列與不犯錯誤的最終排列,有\(k\)個位置不同,那麼犯錯次數\(\lceil\frac{k}{2}\rceil\)。
以此來當做剪枝,枚舉洗牌次數和犯錯次數,跑\(idA^*\)
例六
你記錄了\([0,59]\)這個時間段內到站的所有公交車(數量\(\le 300\)) 的時間,每輛車屬於一條線路。
- 同一路線路的車的到站時間間隔相同。
- 每條線路在\([0,59]\)至少到達兩輛車。
- 最多有\(17\)條線路
求最少可能有多少條線路
解:
一條線路,可以通過第一輛、第二輛車來確定。
我們可以按照到站順序來確定每輛車的歸屬情況。
如果它是線路第一輛車,那麼先把他做一個標記。(同時,它的時間應在\([0,29]\))
如果是第二輛,枚舉標記過的車輛來確定一條線路,如果可行,刪除該線
路的所有車。
這樣,隨著搜尋的深度增加,可選車輛快速減少,搜尋規模大量降低。
同時可以加入若干合法性或者最優性剪枝。比如:作為第一輛的車多於未確定的車。
例七
給定\(n\)個字元集為\(\{a,b,c,\cdots,m\}\)的字串\(S_i\),求一種\(\{a,b,c,\cdots,m\}\)到\(\{0,1,2,3,4,5,6,7,8,9,+,\times,=\}\)的映射\(f\),使得所有\(f(S_i)\)均是運算式合法、且成立的等式。
\(n\le 1000,5\le |S_i|\le 11\)
解:
先對約束條件較高的\(+,\times ,=\)進行搜尋。
然後,對於每一個等式,我們得到了每個數的位元,計算等號兩側的範圍,
無交集就可以剪枝。隨著對應關係的確定,範圍也會越來越小。
可以選擇從低位開始搜,確定了低位的值,不相等可以剪枝
雅禮學習10.3