Team Task:DP
我分到的任务是看ppt……so这篇blog大概就是我的任务进度了?好像还混杂了一些奇怪的求助……
提纲篇
又名如何高效看PPT?
动态规划优化.pdf By ExfJoe
2018/4/11 14:09
第一部分,讲了讲DP的定义……道理我都懂……
第二部分讲模型,还是没有题
计数类DP 矩阵、斯特林数,用恰好为k、大于等于k等限制来描述
期望类DP 期望的线性性,迭代法和消元,注意概率独立
第三部分讲优化
各种优化要满足的条件都是用数学公式给出的,比较整齐好记
不过还是那句话——道理我都懂啊???
滚动数组、队列过滤(像插头DP把有用状态放hash表里什么的)
前后缀、决策单调性($w[i][j]+w[i+1][j+1]<=w[i+1][j]+w[i][j+1]$)
单调队列、斜率优化(斜率和横坐标都递增可以单队做到O(n),否则CDQ或平衡树)
矩阵、递推(多项式求逆,简单提了一句特征多项式;式子构造得蜜汁奇怪,不过在zzh的援助下大致知道了应该怎么处理它?)
容斥原理(+-1、组合数、带mu容斥就是反演的式子,P40有个关于补集的式子)
第四部分开始讲题了,没有部分分的样子……
124一眼题,3也挺简单,56中等题
78910都比较好
有一种目标明确地去做DP题比考场上碰到DP分析出来再去找状态容易得多的错觉……
emmm……总结?
概念部分没什么用……
如果想看题就从第5题开始吧,pdf上的题面和讲解都还不错
其实应该看着pdf完成想题和解题的过程……所以提纲部分先不写题解啦
我去写了的题应该会另外写题解?这只是个针对pdf的总结
我没看懂的部分是P62-65、P76-78的题解,需要队友帮助……
5是hihocoder challenge19 A
6是HDU4809,感觉会了思路之后实现不会特别困难
7是TopCoder SRM641 Hard 加强版???怀疑这题不存在于这个pdf以外的地方
8是Codeforces 722E 从特殊到一般的过程
9是Codechef SEP11 CNTHEX 有点妙啊……意外好像又在情理之中
10……作者写了unknown但是zzh指出它是BZOJ4712
这个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的代码注释。