20170820四校聯考
來看看IOIAu巨神zzx的名言
不用循環輸入就會狗啊哥哥!
上題目:
T1:
填算式(expr)
【題目描述】
填算式是一種簡單的數學遊戲,可以形式化描述如下:n 個數字a1; a2; …… ; an 排成一排(1<= ai<=9),相鄰兩個數字之間有一個空格。你可以在每個空格內填入運算符+- * 之一,也可以不填,要求得到的算式的運算結果等於k。你的任務是計算有多少種不同的正確算式。比如n = 3,3 個數字為2; 2; 2,k = 24時,有兩種不同的正確算式:22 + 2 = 24,2 + 22 = 24。
【輸入格式】
從文件expr.in 中讀入數據。
輸入的第一行包含兩個整數n; k,表示數字個數和要求的答案。
接下來一行,包含n 個整數,第i 個數為ai。相鄰兩個整數用一個空格隔開。
【輸出格式】
輸出到文件expr.out 中。
輸出一個整數,表示不同的正確算式個數。
【樣例1 輸入】
4 11
1 2 3 4
【樣例1 輸出】
3
【樣例1 解釋】
3 個正確的算式為:12 + 3 ?? 4 = 11,1 + 2 3 + 4 = 11,1 ?? 2 + 3 4 = 11。
【樣例2 輸入】
7 1
1 1 1 1 1 1 1
【樣例2 輸出】
241
【樣例3 輸入】
10 3276
7 7 8 6 1 4 1 1 1 4
【樣例3 輸出】
104
【子任務】
子任務會給出部分測試數據的特點。如果你在解決題目中遇到了困難,可以嘗試只
解決一部分測試數據。
每個測試點的數據規模及特點如下表:
題解:
比較簡單的搜索吧(雖然我打掛了)
直接DFS從左到右枚舉每個空格填+,-,×還是不填,然後計算這個式子的值,這樣復雜度是4^(n-1)*n,期望得分95分
在DFS的參數裏面存儲當前運算結果,可以一邊DFS一邊計算,具體做法如下:
維護參數 a,b,c,初始 a=c=0,b=1
不填,則 c->10c+x[i]
填+,則 a->a+bc,b=1,c=x[i]
填-,則 a->a+bc,b=-1,c=x[i]
填×,則 b->bc,c=x[i]
最後 a+bc就是運算結果
復雜度優化到4^(n-1),期望得分100分
部分分算法:
如果不會DFS,可以用三重循環通過測試點1~12,期望得分60分
如果不會循環,可以用條件語句通過測試點1~4或者1~8,期望得分20~40分
代碼:
#include<cstdio> #define r register #define Fn "expr" typedef long long ll; int n,k,ans,x[15]; bool check(ll a,ll b,ll c){return a+b*c==k;} void dfs(int st,ll a,ll b,ll c){ if(st==n){ans+=check(a,b,c);return;} dfs(stView Code+1,a,b,c*10+x[st]); dfs(st+1,a+b*c,1,x[st]); dfs(st+1,a+b*c,-1,x[st]); dfs(st+1,a,b*c,x[st]); } int main(){ freopen(Fn".in","r",stdin); freopen(Fn".out","w",stdout); scanf("%d%d",&n,&k); for(r int i=0;i<n;i++)scanf("%d",x+i); dfs(1,0,1,x[0]); printf("%d\n",ans); return 0; }
T2:
閉合子圖(closure)
【樣例1 輸入】
5 5
1 3
3 4
5 4
1 5
2 1
【樣例1 輸出】
5
題解:
部分分算法:枚舉子集
枚舉 V 的非空子集 S,根據定義判定是否滿足條件,更新答案
判定部分實現優秀的話可以做到O(1),期望得分20~25分
部分分算法:枚舉區間
註意到點集構成的區間只有 n(n+1)/2 個,可以枚舉區間然後將區間內的點作為S,進行判定
直接實現的復雜度 O(n^2*m),期望得分35分
註意到對於一個點u的出邊(u,v),只有最小的v和最大的v是有用的,可以將m縮小到2n
復雜度 O(n^3),期望得分40分
進一步地,可以枚舉左端點,然後從小到大枚舉右端點,維護區間內出邊指向點的最小值和最大值
復雜度 O(n^2),期望得分50分
部分分算法:樹的數據
對於測試點11,12,顯然所有形如[1,i]的區間都滿足條件
所以答案為n,復雜度O(n),期望得分10分,結合上述算法期望得分60分
部分分算法:DAG的數據
對於測試點13,14,判定區間[l,r]是否滿足要求只需要[l,r]內的點指向的點編號均不超過r
用p[i]表示i指向的點的編號(不存在則p[i]=i),則可以枚舉r,找出第一個滿足p[i]>r的i,那麽l=i+1,i+2,...,r都是合法的,答案加上r-i
從左到右維護p[i]的遞減單調棧即可,復雜度O(n),期望得分15分。
結合上述算法期望得分75分
算法:分治
考慮分治,假設當前統計的是[L,R]有多少個子區間合法,取mid=[(L+R)/2],統計包含mid和mid+1的合法區間個數
記p[i],q[i]分別表示i的出邊指向點的最小值和最大值(不存在則為i)
則區間[l,r]合法的條件是
對於l<=i<=mid,p[i]>=l (1) 且 q[i]<=r (2)
對於mid<i<=r,p[i]>=l (3) 且 q[i]<=r (4)
條件(1)只和l有關,條件(4)只和r有關,做一遍前綴/後綴最值把合法的l,r處理出來即可
記Q[l]=max{q[i]|l<=i<=mid},條件(2)就是r>=Q[l]
如果記r‘為最小的r>=mid滿足p[r‘]<l,那麽條件(3)就是r<r‘,且r‘隨l的減小而增大
那麽只需從大到小枚舉l,維護Q[l],單調維護r‘,答案加上[Q[l],r‘)內的合法r個數,這一部分復雜度是線性的
總復雜度O(nlogn),期望得分100分
如果復雜度不小心多寫了一個log可能只有95分
代碼:
#include<cstdio> #define maxn 300010 #define reg register #define Fn "closure" #define mod 1000000007 #define mid (lt+rt>>1) typedef long long ll; int n,m,ans; int l[maxn],r[maxn],s[maxn]; inline int read(){ reg int x=0,f=1;reg char c=getchar(); for(;c<‘0‘||c>‘9‘;f=c==‘-‘?-1:1,c=getchar()); for(;c>=‘0‘&&c<=‘9‘;x=(x<<3)+(x<<1)+c-‘0‘,c=getchar()); return x*f; } void bs(int lt,int rt){ if(rt-lt==1){ans+=l[lt]==lt&&r[lt]==lt;return;} reg int mr=0; s[mid-1]=0; for(reg int i=mid;i<rt;i++){ if(r[i]>mr)mr=r[i]; s[i]=s[i-1]+(mr==i); } reg int ml=1<<30,pos=mr=mid; for(reg int i=mid;i-->lt;){ if(l[i]<ml)ml=l[i]; if(r[i]>mr)mr=r[i]; while(pos<rt&&l[pos]>=i)pos++; if(ml==i&&pos>mr)(ans+=s[pos-1]-s[mr-1])%=mod; } bs(lt,mid); bs(mid,rt); } int main(){ freopen(Fn".in","r",stdin); freopen(Fn".out","w",stdout); n=read();m=read(); for(reg int i=1;i<=n;)l[i]=r[i]=i++; while(m--){ reg int s=read(),t=read(); if(t<l[s])l[s]=t; if(t>r[s])r[s]=t; } bs(1,n+1); printf("%d\n",ans); return 0; }View Code
由於時間原因,T3暫時沒有打,請大佬們諒解!
20170820四校聯考