某大學每一年都會有一次 Mystery Hunt 的活動,玩家須要根據設置的線索解謎,找到寶藏的位置,前一年獲勝的隊伍能夠得到這一年出題的機會。ios
做爲新生的你對這個活動很是感興趣。你天天都要從西向東通過教學樓一條很長的走廊,這條走廊是如此的長,以致於它被人戲稱爲 infinite corridor。一次,你通過這條走廊的時,注意到在走廊的牆壁上隱藏着 \(n\) 個等長的二進制的數字,長度均爲 \(m\)。你從西向東將這些數字記錄了下來,造成一個含有 \(n\) 個數的二進制數組 \(a_1, a_2, ..., a_n\)。很快,在最新的一期 Voo Doo 雜誌上,你發現了 \(q\) 個長度也爲 \(m\) 的二進制串 \(r_1, r_2, ..., r_q\)。聰明的你很快發現了這些數字的含義。保持數組 \(a_1, a_2, ..., a_n\) 的元素順序不變,你能夠在它們之間插入 \(\wedge\)(按位與運算)或者 \(\vee\)(按位或運算)兩種二進制運算符。例如:\(11011 \wedge 00111=00011,11011 \vee 00111=11111\)。數組
你須要插入剛好 \(n\) 個運算符,相鄰兩個數之間剛好一個,在第一個數的左邊還有一個。若是咱們在第一個運算符的左邊補入一個 \(0\),這就造成了一個運算式,咱們能夠計算它的值。與往常同樣,運算順序是從左往右。有趣的是,出題人已經告訴你這個值的可能的集合——Voo Doo 雜誌裏的那一些二進制數 \(r_1, r_2, ..., r_q\),而解謎的方法,就是對 \(r_1, r_2, ..., r_q\) 中的每個值 \(r_i\),分別計算出有多少種方法填入這 \(n\) 個運算符,使得這個運算式的值是 \(r_i\) 。然而,infinite corridor 真的很長,這意味着數據範圍可能很是大。所以,答案也可能很是大,可是你發現因爲謎題的特殊性,你只須要求答案模 \(1000000007\)(\(10^9 + 7\),一個質數)的值。網絡
對於 \(10\%\) 的數據,\(n \le 20, m \le 30\),\(q = 1\)數據結構
對於另外 \(20\%\) 的數據,\(n \le 1000\),\(m \le 16\)測試
對於另外 \(40\%\) 的數據,\(n \le 500\),\(m \le 1000\)優化
對於 \(100\%\) 的數據,\(1 \le n \le 1000\),\(1 \le m \le 5000\),\(1 \le q \le 1000\)ui
orz myy. 神題。spa
發現\(|1\)和\(\& 0\)後的結果是必定的,因此某一位最後爲1,則要求最後一個&0的位置要在|1以前。
聽說這樣從後往前爆搜,及時break能夠獲得70分?!code
而後考慮把操做序列量化成01串,&=1,|=0,則對於某一位來講,從後往前,當操做串的字典序小於運算元素的串,則最後運算結果爲1。排序
這樣就很好處理了,把這m個串摳出來,操做串要小於其中一些字串的字典序,大於等於另外一些的。也就是\(x\le op<y\),把\(x,y\)轉成二進制數後算差就是op的數量了。對這些串排序後算相鄰兩數差,最後要麼沒有答案要麼是某相鄰兩數之差。
注意考場沒有開O2因此最好用Trie樹排序或者雞排。
複雜度\(\mathcal O(nm)\)。
#include<iostream> #include<cstdio> #include<cstdlib> #include<vector> #define pb push_back using namespace std; const int N=1100,M=5100,Tr=5e6+10,mod=1e9+7; int n,m,q,nod=1,ch[Tr][2],rk[M],tt,bit[N],d[M]; char s[N][M]; vector<int> tag[Tr]; struct Num { int s[N],rk; int operator - (Num A) const { int res=0; for(int i=n;i>=1;i--) if(s[i]!=A.s[i]) (res+=1ll*(s[i]-A.s[i]+mod)*bit[n-i+1]%mod)%=mod; return res; } }A[M]; void Insert(Num A,int id) { int x=1; for(int i=1;i<=n;i++) { int &v=ch[x][A.s[i]]; if(!v) v=++nod;x=v; } tag[x].pb(id); } void dfs(int x) { for(int l=tag[x].size(),i=0;i<l;i++) rk[++tt]=tag[x][i]; if(ch[x][0]) dfs(ch[x][0]); if(ch[x][1]) dfs(ch[x][1]); } int main() { cin>>n>>m>>q;bit[1]=1; for(int i=2;i<=n;i++) bit[i]=2ll*bit[i-1]%mod; for(int i=1;i<=n;i++) scanf("%s",s[i]+1); for(int i=1;i<=m;i++) for(int j=n,p=0;j>=1;j--) A[i].s[++p]=s[j][i]-'0'; for(int i=1;i<=m;i++) Insert(A[i],i); dfs(1); for(int i=1;i<=m;i++) A[rk[i]].rk=i; for(int i=1;i<m;i++) d[i]=A[rk[i+1]]-A[rk[i]]; for(int i=1;i<=n;i++) A[m+1].s[i]=0,A[m+2].s[i]=1; d[0]=A[rk[1]]-A[m+1],d[m]=(A[m+2]-A[rk[m]]+1)%mod; for(int w=1;w<=q;w++) { scanf("%s",s[0]+1); int ans=0,mxl=0,mnr=m+1; for(int i=1;i<=m;i++) if(s[0][i]=='1') mnr=min(mnr,A[i].rk); else mxl=max(mxl,A[i].rk); if(mxl<mnr) ans=d[mxl]; printf("%d\n",ans); } return 0; }
一次小 G 和小 H 本來準備去聚餐,但因爲太麻煩了因而題面簡化以下:
一個轉盤上有擺成一圈的 \(n\) 個物品(編號 \(1\) 至 \(n\))其中第 \(i\) 個物品會在 \(T_i\) 時刻出現。
在 \(0\) 時刻時,小 G 能夠任選 \(n\) 個物品中的一個,咱們將其編號記爲 \(s_0\)。而且若是 \(i\) 時刻選擇了物品 \(s_i\),那麼 \(i + 1\) 時刻能夠繼續選擇當前物品或者選擇下一個物品。當 \(s_i\) 爲 \(n\) 時,下一個物品爲物品 \(1\),不然下一個物品爲 \(s_{i} + 1\)。在每一時刻(包括 \(0\) 時刻),若是小 G 所選擇的物品已經出現了,那麼小 G 將會標記它。小 H 想知道,在物品選擇的最優策略下,小 G 何時能標記全部物品?
但麻煩的是,物品的出現時間會不時修改。咱們將其描述爲 \(m\) 次修改,每次修改將改變其中一個物品的出現時間。每次修改以後,你也須要求出當前局面的答案。對於其中部分測試點,小 H 還追加了強制在線的要求。
測試點編號 | \(n\) | \(m\) | \(T_i/T_x\) | \(p\) |
---|---|---|---|---|
1 | \(\le 10\) | \(\le 10\) | \(\le 10\) | \(=0\) |
2 | \(\le 1000\) | \(=0\) | \(\le 1000\) | \(=0\) |
3 | \(\le 10^5\) | \(=0\) | \(\le 10^5\) | \(=0\) |
4 | \(\le 5000\) | \(\le 5000\) | \(\le 10^5\) | \(=0\) |
5 | \(\le 8\times 10^4\) | \(\le 8\times 10^4\) | \(\le 10^5\) | \(=0\) |
6 | \(\le 8\times 10^4\) | \(\le 8\times 10^4\) | \(\le 10^5\) | \(=1\) |
7 | \(\le 9\times 10^4\) | \(\le 9\times 10^4\) | \(\le 10^5\) | \(=0\) |
8 | \(\le 9\times 10^4\) | \(\le 9\times 10^4\) | \(\le 10^5\) | \(=1\) |
9 | \(\le 10^5\) | \(\le 10^5\) | \(\le 10^5\) | \(=0\) |
10 | \(\le 10^5\) | \(\le 10^5\) | \(\le 10^5\) | \(=1\) |
我真佩服去年的本身、居然有40分,而今天看了很久纔看懂去年的作法。
首先能夠證實的是必定是隻走一圈。
去年的40分作法:
序列倍長後,\(a[i]=T[i]-i\),對於a維護單調遞減隊列,答案爲n個滑動窗口的隊頭+i。能夠把a當作是等待時間,最後加上n-1就是真正的答案了。
AC作法:
其實答案求的就是\[min_{i=1}^{n}[max_{j=i}^{i+n}A_j+i]\]。
發現\(A_j>A_{j+n}\)後,式子裏的max就能夠換成後綴max了。考慮用線段樹維護這個東西。
每一個節點\((l,r)\)維護\(mx[x]\)表示最大的A,\(ans[x]\)表示\(i\)取到\([l,mid]\)時候的最小答案。
合併信息就從新遞歸一下,和男神那題超級像。
複雜度\(\mathcal O(nlog^2n)\)。
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; const int N=2e5+10; int n,m,op,T[N],a[N],ans[N<<2],mx[N<<2],Ans; int calc(int x,int l,int r,int b) { if(l==r) return l+max(mx[x],b); int mid=(l+r)>>1; if(mx[x<<1|1]>=b) return min(ans[x],calc(x<<1|1,mid+1,r,b)); else return min(calc(x<<1,l,mid,b),mid+1+b); } void pushup(int x,int l,int r) { mx[x]=max(mx[x<<1],mx[x<<1|1]); ans[x]=calc(x<<1,l,(l+r)>>1,mx[x<<1|1]); } void build(int x,int l,int r) { if(l==r) {mx[x]=a[l],ans[x]=T[l];return;} int mid=(l+r)>>1; build(x<<1,l,mid); build(x<<1|1,mid+1,r); pushup(x,l,r); } void update(int x,int l,int r,int p) { if(l==r) {mx[x]=a[l],ans[x]=T[l];return;} int mid=(l+r)>>1; if(p<=mid) update(x<<1,l,mid,p); else update(x<<1|1,mid+1,r,p); pushup(x,l,r); } int main() { cin>>n>>m>>op; for(int i=1;i<=n;i++) { scanf("%d",&T[i]);a[i]=T[i]-i; T[i+n]=T[i],a[i+n]=T[i]-(i+n); } build(1,1,n*2);printf("%d\n",Ans=ans[1]+n-1); for(int i=1,x,y;i<=m;i++) { scanf("%d%d",&x,&y); if(op) x^=Ans,y^=Ans; T[x]=T[x+n]=y,a[x]=y-x,a[x+n]=y-x-n; update(1,1,n*2,x),update(1,1,n*2,x+n); printf("%d\n",Ans=ans[1]+n-1); } return 0; }
從前有一名毒瘤。
毒瘤最近發現了量產毒瘤題的奧祕。考慮以下類型的數據結構題:給出一個數組,要求支持若干種奇奇怪怪的修改操做(例如給一個區間內的數同時加上 \(c\),或者將一個區間內的數同時開平方根),而且支持詢問區間的和。毒瘤考慮了 \(n\) 個這樣的修改操做,並將它們編號爲 \(1 \ldots n\)。當毒瘤要出數據結構題的時候,他就將這些修改操做中選若干個出來,而後出成一道題。
固然了,這樣出的題有可能不可作。經過精妙的數學推理,毒瘤揭露了這些修改操做之間的關係:有 \(m\) 對「互相排斥」的修改操做,第 \(i\) 對是第 \(u_i\) 個操做和第 \(v_i\) 個操做。當一道題中同時含有 \(u_i\) 和 \(v_i\) 這兩個操做時,這道題就會變得不可作。另外一方面,當一道題中不包含任何「互相排斥」的操做時,這個題就是可作的。此外,毒瘤還發現了一個規律:\(m − n\) 是一個很小的數字(參見「數據範圍」中的說明),且任意兩個修改操做都是連通的。兩個修改操做 \(a, b\) 是連通的,當且僅當存在若干操做 \(t_0, t_1, ... , t_l\),使得 \(t_0 = a,t_l = b\),且對任意 \(1 \le i \le l\),\(t_{i−1}\) 和 \(t_i\) 都是「互相排斥」的修改操做。
一對「互相排斥」的修改操做稱爲互斥對。如今毒瘤想知道,給定值 \(n\) 和 \(m\) 個互斥對,他一共能出出多少道可作的不一樣的數據結構題。兩個數據結構題是不一樣的,當且僅當其中某個操做出如今了其中一個題中,可是沒有出如今另外一個題中。
測試點 # | 1~4 | 5~6 | 7~8 | 9 | 10~11 | 12~14 | 15~16 | 17~20 |
---|---|---|---|---|---|---|---|---|
\(n \le\) | \(20\) | \(10^5\) | \(10^5\) | \(3000\) | \(10^5\) | \(3000\) | \(10^5\) | \(10^5\) |
\(m \le\) | \(n + 10\) | \(n - 1\) | \(n\) | \(n + 1\) | \(n + 1\) | \(n + 10\) | \(n + 7\) | \(n + 10\) |
就是求有11條返祖邊的樹的獨立集個數。
很良心地給了75左右的暴力容斥部分分。
正解:
把11*2個點摳出來建虛樹,一共不到50個點。預處理出沒有返祖邊的樹的dp值。
如今考慮仍然暴力容斥,可是計算過程能夠只用在虛樹上計算,也就是說優化掉一個\(n\)。
發現虛樹上每條邊的轉移係數是必定的,把未知數代進去轉移、就能夠預處理出轉移係數了。
具體來講個人\(g[x][0/1]=(a,b)\),\(x\)的虛樹父親爲\(f\),則
\[ dp[f][0]*=(g[x][0].a*dp[x][0]+g[x][1].a*dp[x][1]); \]
\[ dp[f][1]*=(g[x][0].b*dp[x][0]+g[x][1].b*dp[x][1]); \]
因此顯然初值\(g[x][0]=(1,1),g[x][1]=(1,0)\) 。
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<vector> #define pa pair<int,int> #define fi first #define se second #define mp make_pair #define pb push_back using namespace std; const int N=2e5+10,mod=998244353; struct edge{int next,to;}a[N]; int head[N],cnt,n,m,sf[N],fa[N],f[N][2],ST[N][18]; int dfn[N],S[N],c,tot,dep[N],sta[N],top,out[N]; void link(int x,int y) {a[++cnt]=(edge){head[x],y};head[x]=cnt;} int find(int x) {return sf[x]==x?x:sf[x]=find(sf[x]);} int ksm(int x,int k) { int s=1;for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) s=1ll*s*x%mod;return s; } void dfs(int x,int fr) { fa[x]=fr;ST[x][0]=fr; dep[x]=dep[fr]+1;dfn[x]=++tot; for(int p=1;p<=16;p++) ST[x][p]=ST[ST[x][p-1]][p-1]; f[x][0]=f[x][1]=1; for(int i=head[x];i;i=a[i].next) { int R=a[i].to;if(R==fr) continue; dfs(R,x); f[x][0]=1ll*f[x][0]*(f[R][0]+f[R][1])%mod; f[x][1]=1ll*f[x][1]*f[R][0]%mod; } out[x]=tot; } int LCA(int x,int y) { if(dep[x]<dep[y]) swap(x,y); for(int p=16;p>=0;p--) if(dep[ST[x][p]]>=dep[y]) x=ST[x][p]; for(int p=16;p>=0;p--) if(ST[x][p]!=ST[y][p]) x=ST[x][p],y=ST[y][p]; return x==y?x:ST[x][0]; } void Jump(int &R,int x) { for(int p=16;p>=0;p--) if(dep[ST[R][p]]>dep[x]) R=ST[R][p]; } int cmp(int a,int b) {return dfn[a]<dfn[b];} vector<int> E[N]; int ban[N],g[N][2],s,Ans; pa f0[N],f1[N],M[N]; void Calc(int x,int y) { int p=x; f0[x]=mp(1,1);f1[x]=mp(1,0); while(fa[p]!=y) { int bs0=1,bs1=1; for(int i=head[fa[p]];i;i=a[i].next) { int R=a[i].to; if(R==fa[fa[p]]||R==p) continue; bs0=1ll*bs0*(f[R][0]+f[R][1])%mod; bs1=1ll*bs1*f[R][0]%mod; } pa ff0=mp(1ll*f0[x].fi*bs0%mod,1ll*f0[x].se*bs0%mod); pa ff1=mp(1ll*f1[x].fi*bs1%mod,1ll*f1[x].se*bs1%mod); f0[x]=mp((ff0.fi+ff1.fi)%mod,(ff0.se+ff1.se)%mod); f1[x]=mp(ff0.fi,ff0.se); p=fa[p]; } } int DP() { for(int i=1;i<=c;i++) g[S[i]][1]=1,g[S[i]][0]=ban[S[i]]?0:1; for(int i=c;i>=1;i--) { int x=S[i]; g[x][0]*=f[x][0],g[x][1]*=f[x][1]; for(int j=0,l=E[x].size();j<l;j++) { int R=E[x][j];Jump(R,x); g[x][0]=1ll*g[x][0]*ksm((f[R][0]+f[R][1])%mod,mod-2)%mod; g[x][1]=1ll*g[x][1]*ksm(f[R][0],mod-2)%mod; } for(int j=0,l=E[x].size();j<l;j++) { int R=E[x][j]; g[x][0]=1ll*g[x][0]*(1ll*f0[R].fi*g[R][0]%mod+1ll*f0[R].se*g[R][1]%mod)%mod; g[x][1]=1ll*g[x][1]*(1ll*f1[R].fi*g[R][0]%mod+1ll*f1[R].se*g[R][1]%mod)%mod; } } return (g[1][0]+g[1][1])%mod; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) sf[i]=i; for(int i=1,x,y;i<=m;i++) { scanf("%d%d",&x,&y); if(find(x)!=find(y)) sf[find(x)]=find(y),link(x,y),link(y,x); else M[++s]=mp(x,y),S[++c]=x,S[++c]=y; } dfs(1,0); sort(S+1,S+c+1,cmp); for(int i=2,t=c;i<=t;i++) S[++c]=LCA(S[i-1],S[i]); S[++c]=1;sort(S+1,S+c+1,cmp); c=unique(S+1,S+c+1)-S-1; for(int i=1;i<=c;sta[++top]=S[i],i++) { while(top&&dfn[S[i]]>out[sta[top]]) top--; if(top) E[sta[top]].pb(S[i]),Calc(S[i],sta[top]); } for(int zt=0,d=1;zt<1<<s;zt++) { for(int i=1;i<=s;i++) if(zt&(1<<(i-1))) d=mod-d,ban[M[i].fi]=ban[M[i].se]=1; int res=DP(); (Ans+=1ll*res*d%mod)%=mod; d=1;for(int i=1;i<=s;i++) ban[M[i].fi]=ban[M[i].se]=0; } cout<<Ans<<endl; }
一次小 G 和小 H 在玩尋寶遊戲,有 \(n\) 個房間排成一列,編號爲 \(1,2,…,n\),相鄰房間之間都有 \(1\) 道門。其中一部分門上有鎖(所以須要對應的鑰匙才能開門),其他的門都能直接打開。
如今小 G 告訴了小 H 每把鎖的鑰匙在哪一個房間裏(每把鎖有且只有一把鑰匙),並做出 \(p\) 次指示:第 \(i\) 次讓小 H 從第 \(S_i\) 個房間出發,去第 \(T_i\) 個房間尋寶。可是小 G 有時會故意在指令裏放入死路,而小 H 也不想浪費多餘的體力去嘗試,因而想事先調查清楚每次的指令是否存在一條通路。
你是否能爲小 H 做出解答呢?
測試點編號 | n | m | 其餘特性 |
---|---|---|---|
1 | $ \le 1000 $ | $ \le 1000 $ | 無 |
2 | $ \le 1000 $ | $ \le 1000 $ | 無 |
3 | $ \le 10^5 $ | $ \le 10^5 $ | \(y \le x\) 恆成立 |
4 | $ \le 10^5 $ | $ \le 10^5 $ | \(y \le x\) 恆成立 |
5 | $ \le 10^5 $ | $ \le 10^5 $ | 無 |
6 | $ \le 10^5 $ | $ \le 10^5 $ | 無 |
7 | $ \le 10^6 $ | $ \le 10^6 $ | \(y \le x\) 恆成立 |
8 | $ \le 10^6 $ | $ \le 10^6 $ | \(y \le x\) 恆成立 |
9 | $ \le 10^6 $ | $ \le 10^6 $ | 無 |
10 | $ \le 10^6 $ | $ \le 10^6 $ | 無 |
對於全部數據,保證 \(1 \le n,p \le 10^6\),\(0 \le m < n\),\(1 \le x, y, S_i,T_i < n\),保證 \(x\) 不重複。
因爲本題輸入文件較大,建議在程序中使用讀入優化。
被暴力艹過90分真的無語。省選若是出現這種狀況退役那也沒有什麼辦法了。
方法是對於每一個點維護向左以及向右最多能到的區間。
先用單調棧維護最大可能區間,以後由\([l,r]\)擴展,找到\([LS,l-1]\)中從右往左第一個\(key[i]>r\)的地方,並把\(l\)設置爲\(i+1\)。以後即可以拓展右區間。
因爲右區間的擴展相似於單調棧,因此不難發現拓展次數最多爲\(\mathcal O(n)\)。所以總複雜度爲\(\mathcal O(nlogn)\) 。
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; const int N=1e6+10; int n,m,q,key[N],l[N],r[N],t[N<<2]; int sta[N],top,LS[N],RS[N]; void build(int x,int l,int r) { if(l==r) {t[x]=key[l];return;} int mid=(l+r)>>1; build(x<<1,l,mid); build(x<<1|1,mid+1,r); t[x]=max(t[x<<1],t[x<<1|1]); } int query(int x,int l,int r,int gl,int gr,int bs) { int mid=(l+r)>>1,res=0; if(l>=gl&&r<=gr) { if(t[x]<=bs) return 0; if(l==r) return l; if(t[x<<1|1]>bs) return query(x<<1|1,mid+1,r,gl,gr,bs); else return query(x<<1,l,mid,gl,gr,bs); } if(gr>mid) res=query(x<<1|1,mid+1,r,gl,gr,bs); if(gl<=mid&&!res) res=query(x<<1,l,mid,gl,gr,bs); return res; } int Find(int l,int r,int x) { if(l>r) return l; int p=query(1,1,n,l,r,x); return p?p+1:l; } int main() { cin>>n>>m>>q; for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),key[x]=y; build(1,1,n);LS[1]=1,RS[n]=n; for(int i=2;i<=n;i++) LS[i]=(key[i-1]&&key[i-1]<=i-1)?i:LS[i-1]; for(int i=n-1;i>=1;i--) RS[i]=(key[i]&&key[i]>i)?i:RS[i+1]; for(int i=n;i>=1;i--) { r[i]=i;l[i]=Find(LS[i],i-1,i); while(r[i]<n&&(!key[r[i]]||(key[r[i]]>=l[i]&&key[r[i]]<=r[i]))) r[i]=r[r[i]+1],l[i]=Find(LS[i],l[i]-1,r[i]); } for(int i=1,x,y;i<=q;i++) scanf("%d%d",&x,&y),puts((l[x]<=y&&r[x]>=y)?"YES":"NO"); }
給定 \(n\) 個整數 \(a_1, a_2, \ldots , a_n(0 \le a_i \le n)\),以及 \(n\) 個整數 \(w_1, w_2, …, w_n\)。稱 \(a_1, a_2, \ldots , a_n\) 的一個排列 \(a_{p[1]}, a_{p[2]}, \ldots , a_{p[n]}\) 爲 \(a_1, a_2, \ldots , a_n\) 的一個合法排列,當且僅當該排列知足:對於任意的 \(k\) 和任意的 \(j\),若是 \(j \le k\),那麼 \(a_{p[j]}\) 不等於 \(p[k]\)。(換句話說就是:對於任意的 \(k\) 和任意的 \(j\),若是 \(p[k]\) 等於 \(a_{p[j]}\),那麼 \(k<j\)。)
定義這個合法排列的權值爲 \(w_{p[1]} + 2w_{p[2]} + \ldots + nw_{p[n]}\)。你須要求出在全部合法排列中的最大權值。若是不存在合法排列,輸出 \(-1\)。
樣例解釋中給出了合法排列和非法排列的實例。
對於前 \(20\%\) 的數據,\(1 \le n \le 10\);
對於前 \(40\%\) 的數據,\(1 \le n \le 15\);
對於前 \(60\%\) 的數據,\(1 \le n \le 1000\);
對於前 \(80\%\) 的數據,\(1 \le n \le 100000\);
對於 \(100\%\) 的數據,\(1 \le n \le 500000\),\(0 \le a_i \le n (1 \le i \le n)\),\(1 \le w_i \le 10^9\) ,全部 \(w_i\) 的和不超過 \(1.5 \times 10^{13}\)。
這題的映射關係很是複雜好嘛!但願不要出現這種題目特別難懂的題目了!
把這題映射關係搞清楚後,發現就是\(a[i]->i\),而後在這棵樹(有環無解)上按照拓撲序依次選完全部的點,貢獻爲選某點的時間×該點權值。
這樣大概有40分的狀壓DP,可是考慮正解:
顯然權值小的點要先選,那麼權值最小的點在選完其父親(若是有的話)後,必定立刻被選。
考慮每一個點向父親縮,代價爲父親的siz×該點的val。因而各個聯通塊的權值如何肯定呢?
考慮兩個聯通塊AB,當前時刻爲i,能夠很輕鬆地列出\(W_{AB},W_{BA}\)的式子,相減發現\(\frac{\sum val}{siz}\)小的被先選會更優。
因此用一個set維護每一個點,每次選取最小點向父親合併,最後合成一個點就行了。
這題據說是YALI考過的題,HNOI考完走出考場聽到許多「YALI人AK了」之類的言語,不是很爽快——HNOI有YALI學長出的題。固然不能否認的是YALI確實很強,應該也沒有泄題的狀況。可是我總以爲在這種大賽搬原題是一種極其不負責任的表現。
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; const int N=1e6+10; int n,m,q,key[N],l[N],r[N],t[N<<2]; int sta[N],top,LS[N],RS[N]; void build(int x,int l,int r) { if(l==r) {t[x]=key[l];return;} int mid=(l+r)>>1; build(x<<1,l,mid); build(x<<1|1,mid+1,r); t[x]=max(t[x<<1],t[x<<1|1]); } int query(int x,int l,int r,int gl,int gr,int bs) { int mid=(l+r)>>1,res=0; if(l>=gl&&r<=gr) { if(t[x]<=bs) return 0; if(l==r) return l; if(t[x<<1|1]>bs) return query(x<<1|1,mid+1,r,gl,gr,bs); else return query(x<<1,l,mid,gl,gr,bs); } if(gr>mid) res=query(x<<1|1,mid+1,r,gl,gr,bs); if(gl<=mid&&!res) res=query(x<<1,l,mid,gl,gr,bs); return res; } int Find(int l,int r,int x) { if(l>r) return l; int p=query(1,1,n,l,r,x); return p?p+1:l; } int main() { cin>>n>>m>>q; for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),key[x]=y; build(1,1,n);LS[1]=1,RS[n]=n; for(int i=2;i<=n;i++) LS[i]=(key[i-1]&&key[i-1]<=i-1)?i:LS[i-1]; for(int i=n-1;i>=1;i--) RS[i]=(key[i]&&key[i]>i)?i:RS[i+1]; for(int i=n;i>=1;i--) { r[i]=i;l[i]=Find(LS[i],i-1,i); while(r[i]<n&&(!key[r[i]]||(key[r[i]]>=l[i]&&key[r[i]]<=r[i]))) r[i]=r[r[i]+1],l[i]=Find(LS[i],l[i]-1,r[i]); } for(int i=1,x,y;i<=q;i++) scanf("%d%d",&x,&y),puts((l[x]<=y&&r[x]>=y)?"YES":"NO"); }
W 國的交通呈一棵樹的形狀。W 國一共有 \(n − 1\) 個城市和 \(n\) 個鄉村,其中城市從 \(1\) 到 \(n − 1\) 編號,鄉村從 \(1\) 到 \(n\) 編號,且 \(1\) 號城市是首都。道路都是單向的,本題中咱們只考慮從鄉村通往首都的道路網絡。對於每個城市,恰有一條公路和一條鐵路通向這座城市。對於城市 \(i\),通向該城市的道路(公路或鐵路)的起點,要麼是一個鄉村,要麼是一個編號比 \(i\) 大的城市。沒有道路通向任何鄉村。除了首都之外,從任何城市或鄉村出發只有一條道路;首都沒有往外的道路。從任何鄉村出發,沿着惟一往外的道路走,總能夠到達首都。
W 國的國王小 W 得到了一筆資金,他決定用這筆資金來改善交通。因爲資金有限,小 W 只能翻修 \(n − 1\) 條道路。小 W 決定對每一個城市翻修剛好一條通向它的道路,即從公路和鐵路中選擇一條並進行翻修。小 W 但願從鄉村通向城市能夠儘量地便利,因而根據人口調查的數據,小 W 對每一個鄉村制定了三個參數,編號爲 \(i\) 的鄉村的三個參數是 \(a_i\),\(b_i\) 和 \(c_i\)。假設從編號爲 \(i\) 的鄉村走到首都一共須要通過 \(x\) 條未翻修的公路與 \(y\) 條未翻修的鐵路,那麼該鄉村的不便利值爲
\[ c_i \cdot (ai + x) \cdot (bi + y) \]
在給定的翻修方案下,每一個鄉村的不便利值相加的和爲該翻修方案的不便利值。
翻修 \(n − 1\) 條道路有不少方案,其中不便利值最小的方案稱爲最優翻修方案,小 W 天然但願找到最優翻修方案,請你幫助他求出這個最優翻修方案的不便利值。
共 \(20\) 組數據,編號爲 \(1 ∼ 20\)。
對於編號 \(\le 4\) 的數據,\(n \le 20\);
對於編號爲 \(5 \sim 8\) 的數據,\(a_i, b_i, c_i \le 5,n \le 50\);
對於編號爲 \(9 \sim 12\) 的數據,\(n \le 2000\);
對於全部的數據,\(n \le 20000\),\(1 \le a_i, b_i \le 60\),\(1 \le c_i \le 10^9\),\(s_i, t_i\) 是 \([−n, −1] \cap (i, n − 1]\) 內的整數,任意鄉村能夠經過不超過 \(40\) 條道路到達首都。
聽說這題出題人想複雜了因而成爲了普及題。。。驗題人幹嗎去了啊。。
然而我剛纔苦苦思索十分鐘仍是忘記怎麼作了(去年作的)。。就怕被降智啊!!!
設\(dp[x][a][b]\)表示\(x\)的子樹內,到根還有a條沒有修好的公路、b條沒有修好的鐵路的最小總代價。
沒了。
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define ll long long using namespace std; int read() { char ch=getchar();int h=0,t=1; while(ch!='-'&&(ch>'9'||ch<'0'))ch=getchar(); if(ch=='-')t=-1,ch=getchar(); while(ch>='0'&&ch<='9'){h=h*10+ch-'0';ch=getchar();} return h*t; } const int MAXN=40010; int N,head[MAXN],cnt,L[MAXN],R[MAXN]; int A[MAXN],B[MAXN],C[MAXN]; ll dp[MAXN>>1][41][41]; struct edge{int next,to,w;}a[MAXN<<2]; void link(int x,int y,int w){a[++cnt]=(edge){head[x],y,w};head[x]=cnt;} void Pre(int x,int fa) { for(int i=head[x];~i;i=a[i].next) { int S=a[i].to; if(S==fa)continue; L[S]=L[x],R[S]=R[x]; (a[i].w==1)?L[S]++:R[S]++; Pre(S,x); } } ll DP(int x,int i,int j) { if(x<=N) return dp[x][i][j]; return 1LL*C[x]*(A[x]+i)*(B[x]+j); } void DFS(int x,int fa) { int lc=0,rc=0; for(int i=head[x];~i;i=a[i].next) if(a[i].to!=fa){DFS(a[i].to,x);rc?lc=a[i].to:rc=a[i].to;} if(!lc) return; for(int i=0;i<=L[x];i++) for(int j=0;j<=R[x];j++) dp[x][i][j]=min(DP(lc,i+1,j)+DP(rc,i,j),DP(lc,i,j)+DP(rc,i,j+1)); } int main() { N=read(); memset(head,-1,sizeof(head)); for(int i=1;i<N;i++) { int x=read(),y=read(); if(x<0)x=-x+N; if(y<0)y=-y+N; link(i,x,1);link(x,i,1); link(i,y,2);link(y,i,2); } for(int i=1;i<=N;i++) { int pos=i+N; A[pos]=read(); B[pos]=read(); C[pos]=read(); } Pre(1,0); DFS(1,0); printf("%lld\n",dp[1][0][0]); return 0; }
這套題目能夠說是很是好、質量很是高的啦。
若是今年讓我考這套題目的話,最好的成績是30+40+75+60+40+100,然而算上聯賽也只能踩隊線。
能夠說很是刺激了。
後天加油啊!