我分到的任務是看ppt……so這篇blog大概就是個人任務進度了?好像還混雜了一些奇怪的求助……php
提綱篇html
又名如何高效看PPT?ios
動態規劃優化.pdf By ExfJoe算法
2018/4/11 14:09 數組
第一部分,講了講DP的定義……道理我都懂……數據結構
第二部分講模型,仍是沒有題
計數類DP 矩陣、斯特林數,用剛好爲k、大於等於k等限制來描述
指望類DP 指望的線性性,迭代法和消元,注意機率獨立ide
第三部分講優化
各類優化要知足的條件都是用數學公式給出的,比較整齊好記
不過仍是那句話——道理我都懂啊???
滾動數組、隊列過濾(像插頭DP把有用狀態放hash表裏什麼的)
先後綴、決策單調性($w[i][j]+w[i+1][j+1]<=w[i+1][j]+w[i][j+1]$)
單調隊列、斜率優化(斜率和橫座標都遞增能夠單隊作到O(n),不然CDQ或平衡樹)
矩陣、遞推(多項式求逆,簡單提了一句特徵多項式;式子構造得蜜汁奇怪,不過在zzh的援助下大體知道了應該怎麼處理它?)
容斥原理(+-一、組合數、帶mu容斥就是反演的式子,P40有個關於補集的式子)優化
第四部分開始講題了,沒有部分分的樣子……
124一眼題,3也挺簡單,56中等題
78910都比較好
有一種目標明確地去作DP題比考場上碰到DP分析出來再去找狀態容易得多的錯覺……ui
emmm……總結?
概念部分沒什麼用……
若是想看題就從第5題開始吧,pdf上的題面和講解都還不錯
其實應該看着pdf完成想題和解題的過程……因此提綱部分先不寫題解啦
我去寫了的題應該會另外寫題解?這只是個針對pdf的總結
我沒看懂的部分是P62-6五、P76-78的題解,須要隊友幫助……
5是hihocoder challenge19 A
6是HDU4809,感受會了思路以後實現不會特別困難
7是TopCoder SRM641 Hard 增強版???懷疑這題不存在於這個pdf之外的地方
8是Codeforces 722E 從特殊到通常的過程
9是Codechef SEP11 CNTHEX 有點妙啊……意外好像又在情理之中
10……做者寫了unknown可是zzh指出它是BZOJ4712spa
這個pdf我已經寫了的題有3568,打算去寫910
我爲啥一開始就開了個1000多k的pdf……
動態規劃.ppt By alpq654321
2018/4/12 09:15
Part1 經典題的擴展
lis問題?$n^2$->$nlogn$。
$exp$:給出兩個序列,能夠有限次用另外一個序列對應位置替換這個序列呢(Bestcoder#20 magic balls)?多來幾個樹狀數組就好了嘛。
$exp^2$:同時交換兩個序列中某些元素的位置,每次詢問像上面那樣無限次替換後可否嚴格遞增(BZOJ3526 POI2014 Card)?線段樹區間合併,$s[i][1/0][1/0]$表示區間$i$左邊換不換/右邊換不換可否嚴格遞增,修改就至關於兩個單點賦值從新pushup一遍就能夠了……這東西脫離DP本質了……不過Lis自己也只是個定義未必要用DP解決啊。
$exp^3$:把一個排列從小到大依次插入每次詢問lis(BZOJ3173 TJOI2013最長上升子序列)?智障題,平衡樹維護出來最後序列再樹狀數組一波就好了嘛。
$exp^4$:給十進制數按位求lis,問區間裏lis剛好爲$k$的數的個數呢?數位DP套數位DP,$f[i][j]$表示到第$i$位而lis狀態爲$j$的方案數,$j$原本是$10^{10}$可是它不降且最多大1就能夠用二進制表示變成$2^{10}$了。儘管作過這種套路的題目仍是以爲挺有意思的。
$exp^5$:給出lis求方案數(BZOJ3591 最長上升子序列)?省選模擬賽原題arg,然而我TLE了???反正思路就是三進製表示,不去卡常了。
01揹包問題?$nm$
$exp$:每次詢問刪去物品$i$最大容量爲$j$的揹包(BZOJ3163 HEOI2013Eden的新揹包問題)?低配的消失之物。配置過低以致於能夠算每一個物品前/後的揹包並起來……會作《消失之物》何須用這種方法呢……
$exp^2$:BZOJ2287 POJ Challenge 消失之物?經典題,無需多言。
比起lis這些exp很讓人失望啊……
Part2 基於位運算的DP問題
並非狀壓和插頭……是按位考慮啦。
BZOJ3668 NOI2014起牀困難綜合症:就是個貪心,你們都會。
BZOJ4069 APIO2015巴厘島的雕塑:必須使用數據分治,除此以外挺可作的。
51nod 子集價值:新定義位運算求全部子集運算結果的平方和……去寫的過程當中偶然發現出題人就是ppt做者……好吧好吧。
HDU5270 ZYB loves XOR 2:是原來作Atcoder時作過的題增強版……當時沒寫,如今由於它不是DP可能仍然不會寫。
這部分難道不是隻有T2的後一半和T3是DP嗎……亂分類差評。
Part3 動態規劃中的剪枝
T1聽說是Bestcoder Round#30的D,可是BC好像沒有Round#30……#29和#31之間有一個Valentine's Day Round,它的D看起來並不像啊。而且P38講的優化沒有看懂,由於不知道是怎麼轉化成矩形的,這和前面的講解好像有點不同……
Topcoder SRM 639Div1-Level 2:神奇的逐步優化,須要認真觀察性質,比較值得寫
Topcoder SRM 547Div1-Level 3:聞所未聞……首先DP就很神,還有卡常教程……講這題的P44-P50幾乎全沒看懂
總結……
lis的exp^2和exp^4還有點意思。
巴厘島的雕塑能夠稍微看一下思路……本身想也能想出來。
Part2的後兩題和Part3的三道題都挺不錯的。
這個ppt我寫了Part2的23,還打算去寫Part2的4
這麼說我如今把超過1000k的兩個ppt都看完啦……望天……還有辣麼多(由於只有題目沒有題解才)幾百k的ppt。
題解篇
大概是ppt裏我去寫了的題……從某種角度來說DP專題治好了個人計數恐懼症。
看起來沒有那麼水實際上真心水的題。題目限制最多交換$s$次,實際上最多$n*(n-1)/2$次是有用的,因此$f[i][j][l]$表示到第$i$個數選了$j$個數交換了$l$次的最優解,枚舉每一個數選仍是不選來轉移,第一維顯然能夠滾動。若是把$s$當作$n^2$,時間複雜度$n^4$,空間複雜度$n^3$。
想起來比上一題稍微有難度一點。首先三我的的限制是沒用的,能夠直接求一我的的再乘三。而後定義狀態數組$f[i][j][0/1/2]$表示以$i$爲根的子樹$X-Y$的值爲$j$不選擇$i$/$i$所在聯通塊有奇數個點/$i$所在聯通塊有偶數個點的方案數,注意這裏的$j$是不把$i$所在聯通塊計算進去的。有了狀態定義以後轉移十分容易,就是把兒子向父親合併的過程。須要注意的就是兒子合併後父親原來的狀態都再也不存在了,因此應該用合併後的DP值取代原來的而不是把它累加上去;以及初始化$f[i][0][1]=1$,$f[i][0][0]=2$,由於不選的點可能被另外兩我的選。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 const int sj=310; 7 const int bd=305; 8 const int mod=1e9+7; 9 int n,h[sj],e,l[sj],r[sj],tp; 10 ll f[sj][sj<<1][3],tmp[sj<<1][3],ans; 11 struct B 12 { 13 int ne,v; 14 }b[sj<<1]; 15 void add(int x,int y) 16 { 17 b[e].v=y,b[e].ne=h[x],h[x]=e++; 18 b[e].v=x,b[e].ne=h[y],h[y]=e++; 19 } 20 inline int read() 21 { 22 int jg=0,jk=getchar()-'0'; 23 while(jk<0||jk>9) jk=getchar()-'0'; 24 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 25 return jg; 26 } 27 void dfs(int x,int fx) 28 { 29 f[x][bd][1]=1,f[x][bd][0]=2; 30 l[x]=r[x]=0; 31 for(int i=h[x];i!=-1;i=b[i].ne) 32 if(b[i].v!=fx) 33 { 34 dfs(b[i].v,x),tp=b[i].v; 35 memset(tmp,0,sizeof(tmp)); 36 for(int j=l[x];j<=r[x];j++) 37 for(int k=l[tp]-1;k<=r[tp]+1;k++) 38 { 39 if(j+k<-n||j+k>n) continue; 40 tmp[j+bd+k][0]=(tmp[j+bd+k][0]+f[x][j+bd][0]*f[tp][k+bd][0]+f[x][j+bd][0]*f[tp][k+bd-1][1]+f[x][j+bd][0]*f[tp][k+bd+1][2])%mod; 41 tmp[j+bd+k][1]=(tmp[j+bd+k][1]+f[x][j+bd][1]*f[tp][k+bd][0]+f[x][j+bd][2]*f[tp][k+bd][1]+f[x][j+bd][1]*f[tp][k+bd][2])%mod; 42 tmp[j+bd+k][2]=(tmp[j+bd+k][2]+f[x][j+bd][2]*f[tp][k+bd][0]+f[x][j+bd][1]*f[tp][k+bd][1]+f[x][j+bd][2]*f[tp][k+bd][2])%mod; 43 if(tmp[j+k+bd][0]||tmp[j+k+bd][1]||tmp[j+k+bd][2]) 44 { 45 if(l[x]>j+k) l[x]=j+k; 46 if(r[x]<j+k) r[x]=j+k; 47 } 48 } 49 for(int j=l[x];j<=r[x];j++) 50 memcpy(f[x][j+bd],tmp[j+bd],sizeof(tmp[j+bd])); 51 } 52 } 53 int main() 54 { 55 while(scanf("%d",&n)!=EOF) 56 { 57 memset(h,-1,sizeof(h)); 58 memset(f,0,sizeof(f)); 59 ans=e=0; 60 for(int i=1;i<n;i++) add(read(),read()); 61 dfs(1,0); 62 for(int i=bd+1;i<=bd+n;i++) 63 ans=(ans+(i-bd)*(f[1][i][0]+f[1][i-1][1]+f[1][i+1][2]))%mod; 64 ans=ans*3%mod; 65 printf("%lld\n",ans); 66 } 67 return 0; 68 }
實際上是某NOIP模擬題的高配版本……分析題目能夠發現通過超過$logs$個特殊點時對答案的貢獻都是1,因此只要統計只通過0~$logs$的方案便可,通過0個特殊點的方案就是NOIP模擬題。當時的作法是$f[i]$表示只通過第$i$個點的方案數,而後$f[i]=C(x_i+y_i,x_i)-\sum_{j=1}^{i-1}{f[j]*C(x_i-x_j+y_i-y_j,x_i-x_j)}$。把這種思路推廣到多個點,用$f[i][j]$表示第$j$個通過$i$的方案數,簡單地容斥一下$f[i][j]=C(x_i+y_i,x_i)-\sum_{l=1}^{i-1}{f[l][j]*C(x_i-x_l+y_i-y_l,x_i-x_l)}-\sum_{l=1}^{j-1}{f[i][l]}$,總複雜度$n^2logs$。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #define ll long long 7 using namespace std; 8 inline int read() 9 { 10 int jg=0,jk=getchar()-'0'; 11 while(jk<0||jk>9) jk=getchar()-'0'; 12 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 13 return jg; 14 } 15 const int mod=1e9+7; 16 const int sj=200010; 17 ll fac[sj],inv[sj],f[2010][25],all,ans; 18 int n,m,k,s,bin[25],vl[25],top,mx; 19 struct point 20 { 21 int xa,ya; 22 }p[2010]; 23 int comp(const point&x,const point&y) 24 { 25 return (x.xa==y.xa)?(x.ya<y.ya):(x.xa<y.xa); 26 } 27 ll ksm(ll x,int y) 28 { 29 ll ret=1; 30 while(y) 31 { 32 if(y&1) ret=ret*x%mod; 33 x=x*x%mod,y>>=1; 34 } 35 return ret; 36 } 37 inline ll c(int x,int y) 38 { 39 return fac[x]*inv[y]%mod*inv[x-y]%mod; 40 } 41 int main() 42 { 43 n=read(),m=read(),k=read(),s=read(); 44 for(int i=1;i<=k;i++) p[i].xa=read(),p[i].ya=read(); 45 sort(p+1,p+k+1,comp); 46 p[++k].xa=n,p[k].ya=m; 47 bin[0]=fac[0]=1,vl[0]=s; 48 for(int i=1;i<=20;i++) 49 { 50 bin[i]=bin[i-1]<<1,vl[i]=ceil((double)s/bin[i]); 51 if(vl[i]==1&&!top) top=i; 52 } 53 mx=n+m; 54 for(int i=1;i<=mx;i++) fac[i]=fac[i-1]*i%mod; 55 inv[mx]=ksm(fac[mx],mod-2); 56 for(int i=mx;i>=1;i--) inv[i-1]=inv[i]*i%mod; 57 for(int i=1;i<=k;i++) 58 for(int j=1;j<=top;j++) 59 { 60 f[i][j]=c(p[i].xa+p[i].ya-2,p[i].ya-1); 61 for(int l=1;l<i;l++) 62 if(p[l].ya<=p[i].ya) 63 f[i][j]=(f[i][j]-f[l][j]*c(p[i].ya-p[l].ya+p[i].xa-p[l].xa,p[i].xa-p[l].xa)%mod+mod)%mod; 64 for(int l=1;l<j;l++) 65 f[i][j]=(f[i][j]-f[i][l]+mod)%mod; 66 } 67 all=c(n+m-2,n-1); 68 for(int j=1;j<=top;j++) 69 { 70 all=(all-f[k][j]+mod)%mod; 71 ans=(ans+vl[j-1]*f[k][j])%mod; 72 } 73 ans=(ans+all)%mod; 74 ans=ans*ksm(c(n+m-2,n-1),mod-2)%mod; 75 printf("%I64d",ans); 76 return 0; 77 }
吉老師的題……比較顯然的思路是$f[i][j][k]$表示選到第$i$個數異或和爲$j$與爲$k$的方案數,可是這樣複雜度是$n*4^{13}$,時間空間都很難接受。
考慮一下與運算的性質,只要有一個0它就爲0,那咱們只須要知道這一位有沒有出現過0就行了。能夠用一個二進制數來記錄每一位是否有0,爲了求異或還須要知道每一位1的個數和01總個數的奇偶性。假設異或和爲$x$,與爲$y$,若是有奇數個數則$x$&$y=y$,不然$x$&$y=0$。如今定義$xx$表示$x$去掉全部位都爲1的部分,那麼合法的$x$必須知足$xx$是$y$的補集的子集,全部合法的$xx$與$y$均可以經過枚舉$y$再枚舉子集預處理出來,給每一個數對編號並存下對應的$xx$和$y$,共有$3^{13}$種合法數對。若是咱們有$xx$和$y$,奇數時$x=xx|y$,偶數時$x=xx$;若是咱們有$x$和$y$,$xx=x$&(~$y$)。如今定義狀態數組$f[i][j][0/1]$表示到了第$i$位$xx$和$y$組合的編號爲$j$,選了偶數/奇數個數的方案數,經過$xx$和$y$還原出$x$、$y$進行操做再獲得新的$xx$和$y$來轉移。初始化什麼都沒選,$xx$爲0而$y$爲全集;統計答案時只有兩種狀況合法:選了奇數個數而且$xx$爲0、選了偶數個數而且$xx$、$y$均爲0。須要滾動數組,時間複雜度$n*2*3^{13}$。
回顧一下這道題,之因此要引入$xx$就是由於它和$y$之間有特殊關係,能夠快速預處理獲得,而且能經過它和$y$去還原出$x$。一個數位所有都是1在這道題裏算一個很特殊的狀況,因此針對特殊狀況設計算法是主要突破口;經過新定義獲取有用狀態優化時間和空間複雜度是最大的亮點。
UPD:感受……上面那些全是……實際上暴力轉移用hash表實現,寫得漂亮一點可過???這樣看起來問題的關鍵就只有分析出合法狀態不多了……除此以外還能夠枚舉最後的與,每次DP一遍求異或等於它的方案數,由於去掉爲1的位後異或是與的補集的子集,複雜度也是$n*3^{13}$;不過最後與爲0的部分須要容斥,細節聽起來會多一點。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 inline int read() 7 { 8 int jg=0,jk=getchar()-'0'; 9 while(jk<0||jk>9) jk=getchar()-'0'; 10 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 11 return jg; 12 } 13 const int sj=1594323; 14 int n,a[55],cnt,id[8192][8192],nt,la,an[sj],xo[sj],tx,ty; 15 ll f[2][sj][2],ans; 16 void pre() 17 { 18 for(int i=0,j;i<8192;i++) 19 { 20 j=(~i)&8191; 21 for(int k=j;;k=(k-1)&j) 22 { 23 an[cnt]=i,xo[cnt]=k; 24 id[i][k]=cnt++; 25 if(!k) break; 26 } 27 } 28 } 29 int main() 30 { 31 n=read(); 32 for(int i=1;i<=n;i++) a[i]=read(); 33 pre(); 34 f[0][id[8191][0]][0]=1; 35 for(int i=1;i<=n;i++) 36 { 37 nt=i&1,la=nt^1; 38 for(int j=0;j<cnt;j++) 39 memcpy(f[nt][j],f[la][j],sizeof(f[nt][j])); 40 for(int j=0;j<cnt;j++) 41 for(int k=0;k<=1;k++) 42 { 43 if(!f[la][j][k]) continue; 44 tx=xo[j],ty=an[j]; 45 if(k) tx|=ty; 46 tx^=a[i],ty&=a[i]; 47 tx&=(~ty); 48 f[nt][id[ty][tx]][k^1]+=f[la][j][k]; 49 } 50 } 51 ans=f[nt][id[0][0]][0]; 52 for(int i=1;i<cnt;i++) 53 if(xo[i]==0) 54 ans+=f[nt][i][1]; 55 printf("%lld",ans); 56 return 0; 57 }
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 const int sj=55; 7 const int mod=2332333; 8 int n,a[sj],bin[sj],nt,la,a1,a2,tot; 9 ll ans; 10 struct hash_table 11 { 12 struct B 13 { 14 int ne,v; 15 ll w; 16 }b[1594323]; 17 int h[mod],e; 18 inline void clear() 19 { memset(h,-1,sizeof(h));e=0; } 20 inline ll &operator [] (int x) 21 { 22 for(int i=h[x%mod];i!=-1;i=b[i].ne) 23 if(b[i].v==x) return b[i].w; 24 b[e].v=x,b[e].w=0,b[e].ne=h[x%mod],h[x%mod]=e++; 25 return b[e-1].w; 26 } 27 }f[2]; 28 int main() 29 { 30 scanf("%d",&n); 31 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 32 bin[0]=1; 33 for(int i=1;i<=26;i++) bin[i]=bin[i-1]<<1; 34 tot=bin[13]-1; 35 f[0].clear(); 36 for(int i=1;i<=n;i++) 37 { 38 nt=i&1,la=nt^1; 39 f[nt]=f[la]; 40 a1=a[i]+(a[i]<<13); 41 f[nt][a1]++; 42 for(int j=0;j<f[la].e;j++) 43 { 44 a1=f[la].b[j].v&tot,a2=f[la].b[j].v>>13; 45 a1&=a[i],a2^=a[i]; 46 a1+=a2<<13; 47 f[nt][a1]+=f[la].b[j].w; 48 } 49 } 50 for(int j=0;j<f[nt].e;j++) 51 { 52 a1=f[nt].b[j].v&tot,a2=f[nt].b[j].v>>13; 53 if(a1==a2) ans+=f[nt].b[j].w; 54 } 55 printf("%lld",ans); 56 return 0; 57 }
清新小水題,除了數據分治這點算是很是規操做,然而兩部分思路相近也都很好寫不算難以接受。先考慮前四個subtask知足$n<=100$,從高位到低位貪心地看這一位可否爲1,用$f[i][j]$表示到$i$分了$j$組是否能使這一位不出現1。假設咱們正在處理第$l$位,從$f[k][j-1]$轉移到$f[i][j]$須要知足$f[k][j-1]==1$&&$(sum[i]-sum[k])$&$bin[i]$==0&&$(((sum[i]-sum[k])|ans)>>i)==(ans>>i)$,若是最後$f[n][a]$~$f[n][b]$裏有合法的則這一位能夠爲0。對於最後一個$n<=2000$的subtask組數沒有下界,能夠用$g[i]$表示到$i$最少分多少組能夠不出現1,轉移方式基本同上,最後只要比較$g[n]$與$B$的大小關係就能夠知道這一位可否爲0了。WA了兩發由於沒考慮到單個值不炸int前綴和就炸了,二進制數位沒開夠……
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 const int sj=2010; 7 int n,a,b,v[sj],g[sj]; 8 ll sum[sj],ans,bin[50]; 9 bool op,f[105][105]; 10 inline int read() 11 { 12 int jg=0,jk=getchar()-'0'; 13 while(jk<0||jk>9) jk=getchar()-'0'; 14 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 15 return jg; 16 } 17 void work1() 18 { 19 for(int i=49;i>=0;i--) 20 { 21 memset(f,0,sizeof(f)); 22 f[0][0]=1,op=0; 23 for(int j=1;j<=b;j++) 24 for(int k=1;k<=n;k++) 25 for(int l=0;l<k;l++) 26 if(f[l][j-1]&&((sum[k]-sum[l])&bin[i])==0&&(((sum[k]-sum[l])|ans)>>i)==(ans>>i)) 27 f[k][j]=1; 28 for(int j=a;j<=b;j++) 29 if(f[n][j]) 30 { op=1;break; } 31 if(!op) ans+=bin[i]; 32 } 33 printf("%lld",ans); 34 } 35 void work2() 36 { 37 for(int i=49;i>=0;i--) 38 { 39 memset(g,0x3f,sizeof(g)); 40 g[0]=0; 41 for(int j=1;j<=n;j++) 42 for(int k=0;k<j;k++) 43 if(g[k]+1<g[j]&&((sum[j]-sum[k])&bin[i])==0&&(((sum[j]-sum[k])|ans)>>i)==(ans>>i)) 44 g[j]=g[k]+1; 45 if(g[n]>b) ans+=bin[i]; 46 } 47 printf("%lld",ans); 48 } 49 int main() 50 { 51 n=read(),a=read(),b=read(); 52 for(int i=1;i<=n;i++) 53 v[i]=read(),sum[i]=sum[i-1]+v[i]; 54 bin[0]=1; 55 for(int i=1;i<50;i++) bin[i]=bin[i-1]<<1; 56 if(n<=100) work1(); 57 else work2(); 58 return 0; 59 }
若是所求不是平方和的話思路很是好想,統計第$j$位時用$f[i][0/1]$表示到第$i$個數爲0/1的方案數,最後用$ans=\sum{bin[j]*f[n][1]}$。如今咱們所求是平方和,能夠把平方拆開,假設最後獲得的數$x$二進制表示爲$a_1+a_2+a_3$,$x^2=a_1^2+a_2^2+a_3^2+2*a_1*a_2+2*a_1*a_3+2*a_2*a_3$,平方項其實也能夠當作同一位的兩個$a_i$相乘,那麼對於每兩位$i$、$j$對答案的貢獻都爲$i$、$j$同時爲1的方案數$×2^{i+j}$。因此枚舉每兩位,用$f[i][0/1][0/1]$表示到第$i$個數這兩位分別爲0/1的方案數。實現上要注意初始化問題,我剛開始初始化$f[0][0][0]=1$,可是這樣並不能準確地表達出「選取子集」的含義(至關於空集一直在狀態裏,但它自己不合法);正確的方法是在DP到第$k$個數時假設$b_k$的兩個二進制位分別爲$d$、$q$,$f[k][d][q]++$,這樣就能表示從當前位開始選取了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 const int sj=50010; 7 const int mod=1e9+7; 8 int n,p,a[2][2],b[sj],bin[30],f[sj][2],g[sj][2][2]; 9 ll ans; 10 bool d,q; 11 inline int read() 12 { 13 int jg=0,jk=getchar()-'0'; 14 while(jk<0||jk>9) jk=getchar()-'0'; 15 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 16 return jg; 17 } 18 int main() 19 { 20 n=read(),p=read(); 21 for(int i=0;i<=1;i++) 22 for(int j=0;j<=1;j++) 23 a[i][j]=read(); 24 for(int i=1;i<=n;i++) b[i]=read(); 25 bin[0]=1; 26 for(int i=1;i<p;i++) bin[i]=bin[i-1]<<1; 27 for(int i=0;i<p;i++) 28 for(int j=0;j<p;j++) 29 { 30 memset(g[0],0,sizeof(g[0])); 31 for(int k=1;k<=n;k++) 32 { 33 memcpy(g[k],g[k-1],sizeof(g[k])); 34 d=b[k]&bin[i],q=b[k]&bin[j]; 35 g[k][d][q]=(g[k][d][q]+1)%mod; 36 for(int l=0;l<=1;l++) 37 for(int z=0;z<=1;z++) 38 g[k][a[l][d]][a[z][q]]=(g[k][a[l][d]][a[z][q]]+g[k-1][l][z])%mod; 39 } 40 ans=(ans+1ll*bin[i]*bin[j]%mod*g[n][1][1])%mod; 41 } 42 printf("%lld",ans); 43 return 0; 44 }
交流篇
一羣毒瘤換題作……
詢問數太多了顯然要預處理。對於每個字母$k$單獨統計,$f[i][j]$表示從$i$到$j$中$k$的個數,$g[k][j]$表示字母$k$替換$j$個位置的最長長度。一邊統計$f[i][j]$一邊更新$g[k][j-i+1-f[i][j]]$,最後別忘了$g[k]$要取前綴最大。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int sj=1510; 6 int n,q,m,f[sj][sj],g[26][sj],nt,la; 7 char s[sj],p[4]; 8 inline int read() 9 { 10 int jg=0,jk=getchar()-'0'; 11 while(jk<0||jk>9) jk=getchar()-'0'; 12 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 13 return jg; 14 } 15 inline void maxx(int &x,int y) 16 { 17 x=x>y?x:y; 18 } 19 int main() 20 { 21 n=read(); 22 scanf("%s",s+1); 23 q=read(); 24 for(int k=0;k<26;k++) 25 { 26 memset(f,0,sizeof(f)); 27 for(int i=1;i<=n;i++) 28 for(int j=i;j<=n;j++) 29 { 30 f[i][j]=f[i][j-1]; 31 if(s[j]-'a'==k) f[i][j]++; 32 maxx(g[k][j-i+1-f[i][j]],j-i+1); 33 } 34 for(int i=1;i<=n;i++) 35 maxx(g[k][i],g[k][i-1]); 36 } 37 for(int i=1;i<=q;i++) 38 { 39 m=read(); 40 scanf("%s",p); 41 printf("%d\n",g[p[0]-'a'][m]); 42 } 43 return 0; 44 }
把我從0or100中拯救出來的有部分分的好題,這才叫OI嘛。前兩個subtask聽說能夠暴搜,DFS有6分BFS有17分,然而我BFS了一發沒hash記憶化MLE了只拿了6分……
以後的多項式複雜度算法須要分析一些結論。首先在何時交換兩個不相鄰元素是沒有關係的,因此能夠一開始就枚舉交換點。其次交換相鄰元素的次數等於逆序對數,這個稍微想想仍是很好理解的,只要是逆序遲早須要交換它們一次且只用交換一次。提到逆序對咱們就頗有講頭了,在$n^2$枚舉最開始交換哪兩個元素的基礎上逐步優化。subtask3的16分容許$n^2$暴力求逆序對,subtask4的8分用$nlog$求逆序對的套路就能夠解決。再分析一下每次只在原序列基礎上動兩個元素,能夠只求出這兩個元素對逆序對的影響,主席樹隨便維護一下權值。若是用「僞裝把它們刪掉再換位插回去」的思路要在主席樹裏查不少次只能過subtask5,分析一下只有兩點中間權值落在兩個修改點中間的元素會影響答案就能夠只查一次過掉subtask6。如今求逆序對部分的複雜度就只有$log$了,到$n^2logn$爲止能拿到61分。若是不用數據結構直接維護二維前綴和能夠直接$O(1)$求出變化後的逆序對數經過subtask7……數據結構就算好寫也不要貿然去打啊。
從如今開始邁上通往AC的道路……實際上好像已經到了。把下標看做$x$軸權值看做$y$軸,咱們是在找一個以兩點分別爲左上角和右下角、包含點最多的矩形——這不就是省選模擬題《圈草地》嗎?大體就是左上角、右下角的最優勢縱座標必定都是單調的,因此把全部點分紅左上方、右下方和中間三種並用ABC表示。用線段樹維護每一個A類點右下方的權值,按橫座標從小到大掃全部點。碰到A類點就把它的位置初始化,碰到B類點把它上方A的權值+1;碰到C類點把它下方的B類點刪去並消掉貢獻,再查詢它上方A類點的最大權值。B類點能夠用set維護,總複雜度$nlogn$。
感受我又被騙來作了一道不像DP的題……從頭至尾這哪DP了這。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 const int sj=300010; 7 int n,a[sj],rt[sj],cnt; 8 ll tp,nt,ans; 9 inline int read() 10 { 11 int jg=0,jk=getchar()-'0'; 12 while(jk<0||jk>9) jk=getchar()-'0'; 13 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 14 return jg; 15 } 16 struct tree 17 { 18 int lc,rc,sum; 19 }t[sj*50]; 20 void insert(int &x,int y,int l,int r,int v) 21 { 22 if(!x) x=++cnt; 23 t[x].lc=t[y].lc,t[x].rc=t[y].rc,t[x].sum=t[y].sum+1; 24 if(l==r) return; 25 int mid=l+r>>1; 26 if(v<=mid) t[x].lc=++cnt,insert(t[x].lc,t[y].lc,l,mid,v); 27 else t[x].rc=++cnt,insert(t[x].rc,t[y].rc,mid+1,r,v); 28 } 29 int query(int x,int la,int l,int r,int z,int y) 30 { 31 if(!x&&!la) return 0; 32 if(z<=l&&r<=y) return t[la].sum-t[x].sum; 33 int mid=l+r>>1; 34 if(y<=mid) return query(t[x].lc,t[la].lc,l,mid,z,y); 35 if(z>mid) return query(t[x].rc,t[la].rc,mid+1,r,z,y); 36 return query(t[x].lc,t[la].lc,l,mid,z,y)+query(t[x].rc,t[la].rc,mid+1,r,z,y); 37 } 38 int main() 39 { 40 n=read(); 41 for(int i=1;i<=n;i++) 42 { 43 a[i]=read(); 44 insert(rt[i],rt[i-1],1,n,a[i]); 45 tp+=query(rt[0],rt[i-1],1,n,a[i],n); 46 } 47 ans=tp; 48 for(int i=1;i<=n;i++) 49 for(int j=i+1;j<=n;j++) 50 { 51 if(a[i]<a[j]) continue; 52 nt=tp-2*query(rt[i],rt[j-1],1,n,a[j],a[i]); 53 if(nt<ans) ans=nt; 54 } 55 printf("%lld",ans); 56 return 0; 57 }
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<set> 5 #define ll long long 6 #define inf 0x7fffffff 7 using namespace std; 8 typedef pair<int,int> pa; 9 const int ss=300010; 10 int n,a[ss],ans,tp,q[ss],top,mx[ss],nt,d[ss]; 11 bool ad[ss],bd[ss]; 12 ll qwq; 13 set<pa> s; 14 set<pa>::iterator it,it2; 15 struct tree 16 { 17 int l,r,sum,lazy; 18 }t[ss<<2]; 19 inline int read() 20 { 21 int jg=0,jk=getchar()-'0'; 22 while(jk<0||jk>9) jk=getchar()-'0'; 23 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 24 return jg; 25 } 26 void build(int x,int l,int r) 27 { 28 t[x].l=l,t[x].r=r,t[x].sum=-inf; 29 if(l==r) return; 30 int mid=l+r>>1; 31 build(x<<1,l,mid),build(x<<1|1,mid+1,r); 32 } 33 inline int maxx(int x,int y) 34 { 35 return x>y?x:y; 36 } 37 void pushdown(int x) 38 { 39 if(t[x].lazy) 40 { 41 t[x<<1].lazy+=t[x].lazy; 42 t[x<<1|1].lazy+=t[x].lazy; 43 t[x<<1].sum+=t[x].lazy; 44 t[x<<1|1].sum+=t[x].lazy; 45 t[x].lazy=0; 46 } 47 } 48 void modify(int x,int pos) 49 { 50 if(t[x].l==t[x].r) 51 { 52 t[x].sum=1; 53 return; 54 } 55 int mid=t[x].l+t[x].r>>1; 56 pushdown(x); 57 if(pos<=mid) modify(x<<1,pos); 58 else modify(x<<1|1,pos); 59 t[x].sum=maxx(t[x<<1].sum,t[x<<1|1].sum); 60 } 61 void update(int x,int z,int y,int v) 62 { 63 if(t[x].l==z&&t[x].r==y) 64 { 65 t[x].sum+=v; 66 t[x].lazy+=v; 67 return; 68 } 69 int mid=t[x].l+t[x].r>>1; 70 pushdown(x); 71 if(y<=mid) update(x<<1,z,y,v); 72 if(z>mid) update(x<<1|1,z,y,v); 73 if(z<=mid&&y>mid) 74 update(x<<1,z,mid,v),update(x<<1|1,mid+1,y,v); 75 t[x].sum=maxx(t[x<<1].sum,t[x<<1|1].sum); 76 } 77 int query(int x,int z,int y) 78 { 79 if(t[x].l==z&&t[x].r==y) return t[x].sum; 80 int mid=t[x].l+t[x].r>>1; 81 pushdown(x); 82 if(y<=mid) return query(x<<1,z,y); 83 if(z>mid) return query(x<<1|1,z,y); 84 return maxx(query(x<<1,z,mid),query(x<<1|1,mid+1,y)); 85 } 86 inline int lowbit(int x) 87 { 88 return x&(-x); 89 } 90 inline int getsum(int x) 91 { 92 int ret=0; 93 while(x) ret+=d[x],x-=lowbit(x); 94 return ret; 95 } 96 void update(int x,int v) 97 { 98 while(x<=n) d[x]+=v,x+=lowbit(x); 99 } 100 int main() 101 { 102 n=read(); 103 build(1,1,n); 104 for(int i=1;i<=n;i++) 105 { 106 a[i]=read(); 107 if(!top||a[i]>a[q[top]]) q[++top]=i,ad[i]=1; 108 qwq+=i-getsum(a[i])-1,update(a[i],1); 109 } 110 top=0; 111 for(int i=n;i>=1;i--) 112 if(!top||a[i]<a[q[top]]) 113 q[++top]=i,bd[i]=1; 114 for(int i=1;i<=n;i++) 115 { 116 if(bd[i]) 117 { 118 for(it=s.begin();it!=s.end()&&(*it).first<a[i];it=it2) 119 { 120 update(1,(*it).first,mx[(*it).second],-1); 121 it2=it,it2++; 122 s.erase(it); 123 } 124 tp=query(1,a[i],n)+1; 125 if(tp>ans) ans=tp; 126 } 127 if(ad[i]) modify(1,a[i]),nt=a[i]; 128 if(!ad[i]&&!bd[i]) 129 { 130 update(1,a[i],nt,1); 131 mx[i]=nt,s.insert(make_pair(a[i],i)); 132 } 133 } 134 if(ans>2) qwq-=(ans-2)*2; 135 printf("%lld",qwq); 136 return 0; 137 }
我會寫20分$n*2^n$暴力……這個總算是真DP題了,真·不會。miskcoo題解和popoqqq的代碼註釋。