时间限制: 2 Sec 内存限制: 256 MB
题目描述
输入
输出
输出一个整数表示答案
样例输入
3 2
101
1 10
1 10
0 20
1 30
样例输出
40
第3行到第6行表示长度为2的4种01串合并方案。00->1,得10分,01->1得10分,10->0得20分,11->1得30分。
题解
考场上本来是想打dfs的,因为犯了些未可知的错误传参死活搞不对(论不会用string和charSTL的恶果),到收卷都没调出来,只get到一个似乎随机不确定出样例版本,最终弃疗输出样例。测完之后全场只有wq大佬拿了5分2333,后来发现是测试点的问题重测后还是有几个dalao拿了几十分的。然后一脸惊奇地发现它原来是个dp,而且还是个状压+区间dp?话说前几天在BZOJ上看到《祖玛》那题,本来以为是个小游戏系列暴搜,跃跃欲试想打一打,但是数据范围有300,查了一下发现是个区间dp,然后就果断没做。
初始的状态数组是f[i][j][s],表示把i到j合并到s状态所得的分数,s是一个二进制数;转移的过程使用g数组。刚开始读入的时候就把f[i][i][a[i]]设为0,其他的都设为负数表示不合法。因为前面的状态需要从后面转移,所以整个过程从后向前进行。用一个g[j][s]数组表示转移到j在s状态下的最大收益,这样f数组最后一维可以只有01表示全部转为0或全部转为1。dp反正是要无差枚举,过程中最重要的就是把一层层for循环堆出来啊(smg)。第一层从n-k+1枚举到1,i表示合并的起点,提前处理一下cnt=1(当前处理了几个)和g[i][0/1]直接赋成f[i][i][0/1];第二层从i+1到n枚举j,表示从i向后合并;第三层从0到(1<<cnt)-1枚举二进制状态s;第四层从j到n枚举从j转移到p,状态转移方程为g[p][s<<1]=max(g[p][s<<1],g[j-1][s]+f[j][p][0]),g[p][(s<<1)|1]=max(g[p][(s<<1)|1],g[j-1][s]+f[j][p][1]),注意必须由大于等于0的合法状态转移而来。每当j++就把cnt也++,cnt达到k时都要从g[j][q]+w[q](0<=q<(1<<cnt))中选择最大的更新tp[ai[q]],再把g[i][1/0]、f[i][j][1/0]都用tp[1/0]更新。当这个一层套一层的循环结束后再从1到n枚举i,从i到n枚举j,更新出最优的ans[j]=max(ans[j],ans[i-1]+max(f[i][j][1],f[i][j][0])),最后的结果就是ans[n]。刚开始还觉得自己的区间dp不算差来着,做了一道这么复杂的题确实有点接受无能啊。
完全不想写题解啊这道题。但是还是要写……本来就懵不写岂不是更懵。当我有所克服的时候,我总是快乐的(倒不如说克服之后)。T2是真克服不了啊……总感觉有些真正超乎我能力范围的题,即使过了心里也不是很明白(参见底部图)。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int sj=305; int n,k,temp,a[sj],ai[sj],cnt; long long w[sj],ans[sj],f[sj][sj][2],g[sj][1<<9],tp[2]; void bj(long long &x,long long y) { x=x>y?x:y; } int main() { scanf("%d%d",&n,&k); temp=1<<k; memset(f,-1,sizeof(f)); for(int i=1;i<=n;i++) { scanf("%1d",&a[i]); f[i][i][a[i]]=0; } for(int i=0;i<temp;i++) scanf("%d%lld",&ai[i],&w[i]); for(int i=n-k+1;i>=1;i--) { memset(g,-1,sizeof(g)); cnt=1; g[i][0]=f[i][i][0],g[i][1]=f[i][i][1]; for(int j=i+1;j<=n;j++) { for(int s=0;s<(1<<cnt);s++) if(g[j-1][s]>=0) for(int p=j;p<=n;p+=k-1) { if(f[j][p][0]>=0) bj(g[p][s<<1],g[j-1][s]+f[j][p][0]); if(f[j][p][1]>=0) bj(g[p][(s<<1)|1],g[j-1][s]+f[j][p][1]); } if(++cnt==k) { tp[0]=tp[1]=-1; for(int q=0;q<(1<<cnt);q++) if(g[j][q]>=0) bj(tp[ai[q]],w[q]+g[j][q]); g[j][1]=f[i][j][1]=tp[1]; g[j][0]=f[i][j][0]=tp[0]; cnt=1; } } } for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=k-1) bj(ans[j],ans[i-1]+max(f[i][j][1],f[i][j][0])); printf("%lld",ans[n]); return 0; }
题外话
大暑假集训这么快也要结束了,有点舍不得。传说中大暑假集训是能力提升最快的时候,确实如此。原来的集训总是觉得相对平时并没有什么优势,全天的学习效率反而不如其他同学高;这一次却不一样,可能是真正地享受了过程吧。
到今天大概是考了15场试,明天据说会有第16场2333。开始几天是校内测试,题目质量比较高,每天主要的目标也就是研究考试题。前五天成绩起伏不太大,考得好的有两三次,考得差的也不算特别差;从第六天开始有几天写得极其之挂,啥都能写挂既视感。之后开始联考,前两三天是欢乐的NOIP模拟赛,题水没有区分度;联考中间也有几天题目难度比较大的,考得有好有坏;联考最后一天回归到水题欢乐赛,愉快地告别联考。然后就是今天了,一下又开始做本校高质量的题目有点不适应,不过成绩还好。感觉这么多场考试下来,擅长的还是图论和dp,薄弱的是数学和数据结构,最容易拉开差距的居然是规律题……但是不管考好考差都能接受,成绩也只不过是一整个上午的一个总结,get新的技能点才是长远的事。倒是没有觉得我在HZOI或者联考非要拿到什么名次,反正享受答题过程把该拿的分拿到就好。比起成绩很差的时候,更难过的其实是没能把自己的想法写出来或写对。最后五天更加看轻成绩,反而状态更好。考试很特别的一点就是可以充分想部分分,而且要对算法的严谨性可行性仔细验证,题与题之间也要有取舍抉择,其实是个非常有意思的过程,而且信奥的考试大概是五科里最好玩的吧。
这段时间里学会了网络流,打了平衡树的板子(虽然说Splay至今背不下来,区间操作不是很会打,开学弄到教室去背~)。网络流掌握得不错,可能也和学长讲得很清楚有关。平衡树在考场上总是不想用,大概是没有太熟打起来也很需要时间。卡特兰数没有做过题,计算几何莫名打过了板子之后打别的题打不过,卡得时间长了去做专题专练就又留坑了。不过把对拍学会了,但是今天考试时间紧还全是暴力没用上。暴搜方面好像已经被虐了个遍,已经不像一开始那样困难了。图论知识点除了差分约束全部过了一遍(最短路、最小生成树、拓扑排序、欧拉遍历、二分图、LCA、树上差分),好多忘掉的东西都回忆起来了。但是还是有一些其他版块的板子不会打,比如说树链剖分(lyy:树链剖分哪不好记了我打得最熟的就是树链剖分2333)、AC自动机、RMQ什么的。
现在有一个什么玩意的问题——文化课一调?什么玩意^n。上一次做高考题是在小二调考场上,假期作业一(da)点(an)没写(bei),即将实现28->146->1460的飞跃。但是想想我不会56也不会,我这么会骗分的人怎么能骗不过56呢2333,谈起文化课我对55还是很有自信哒(flag一立);为了模拟大计一调最好还是好好考不要留下个烂摊子二调一脸什么鬼,也为了下个月过得轻松些可以高考摸鱼学奥赛,要不然很可能被天天查水表啊。这种情况下只能拼信仰了,反正我又不是没学过,不就是没做过原题(新题还不会)吗。
从NOIP2016get了一个好表情包23333期待10月的集训。