洛谷传送门php
JDOJ传送门数组
小F 的学校在城市的一个偏僻角落,全部学生都只好在学校吃饭。学校有一个食堂,虽然简陋,但食堂大厨总能作出让同窗们满意的菜肴。固然,不一样的人口味也不必定相同,但每一个人的口味均可以用一个非负整数表示。 因为人手不够,食堂每次只能为一我的作菜。作每道菜所需的时间是和前一道菜有关的,若前一道菜的对应的口味是a,这一道为b,则作这道菜所需的时间为(a or b)-(a and b),而作第一道菜是不须要计算时间的。其中,or 和and 表示整数逐位或运算及逐位与运算,C语言中对应的运算符为“|”和“&”。 学生数目相对于这个学校仍是比较多的,吃饭作菜每每就会花去很多时间。所以,学校食堂偶尔会不按照你们的排队顺序作菜,以缩短总的进餐时间。 虽然同窗们可以理解学校食堂的这种作法,不过每一个同窗仍是有必定容忍度的。也就是说,队伍中的第i 个同窗,最多容许紧跟他身后的Bi 我的先拿到饭菜。一旦在此以后的任意同窗比当前同窗先拿到饭,当前同窗将会十分愤怒。所以,食堂作菜还得照顾到同窗们的情绪。 如今,小F 想知道在知足全部人的容忍度这一前提下,本身的学校食堂作完这些菜最少须要多少时间。测试
第一行包含一个正整数C,表示测试点的数据组数。 每组数据的第一行包含一个正整数N,表示同窗数。 每组数据的第二行起共N行,每行包含两个用空格分隔的非负整数Ti和Bi,表示按队伍顺序从前日后的每一个同窗所需的菜的口味和这个同窗的忍受度。 每组数据之间没有多余空行。spa
包含C行,每行一个整数,表示对应数据中食堂完成全部菜所需的最少时间。设计
2 5 5 2 4 1 12 0 3 3 2 2 2 5 0 4 0code
16 1ip
对于第一组数据:
同窗1容许同窗2或同窗3在他以前拿到菜;同窗2容许同窗3在他以前拿到菜;同窗3比较小气,他必须比他后面的同窗先拿菜……
一种最优的方案是按同窗三、同窗二、同窗一、同窗四、同窗5作菜,每道菜所需的时间分别是0、八、一、6及1。get
【数据规模和约定】
对于30%的数据,知足1 ≤ N ≤ 20。
对于100%的数据,知足1 ≤ N ≤ 1,000,0 ≤ Ti ≤ 1,000,0 ≤ Bi ≤ 7,1 ≤ C ≤ 5。
存在30%的数据,知足0 ≤ Bi ≤ 1。
存在65%的数据,知足0 ≤ Bi ≤ 5。
存在45%的数据,知足0 ≤ Ti ≤ 130。input
2019.11.6模拟赛T1 爆蛋场string
由于蒟蒻对状压不是很熟悉,因此一开始根本没往那边想,发现能够暴力生成全排列看看合不合法。而后持续更新答案。预计30\(pts\)可是写挂了
那么讲一下这个正解。(由于蒟蒻才学状压,因此不少地方都有初学者的痕迹,请大佬们海涵)
一开始迟迟没法理解这道题为啥能用\(DP\),由于这道题对于每个人来说,他何时打饭既和他前面的人有关(能不能容忍他打饭),也跟后面的人有关(他能不能容忍他们打饭)。在个人印象中,这就叫后效性,是不符合\(DP\)的条件的。
因此须要用状态压缩解决
是的,状态压缩就是把状态化成二进制数存进数组中,而后保证当前状态下转移时是无后效性的。(多么妙啊)
那么咱们考虑状态的设计思路:
首先,这个东西是必定要与枚举到的人有关的,因此开一维存当前枚举到哪一个人。
其次,由于这个东西的转移边界是容忍后面的人吃没吃饭。那么就会有一维存状态:表示\(i\)和\(i\)后面\(7\)我的到底吃没吃饭。
最后,由于这个转移还和前面的人能不能忍你先吃饭有关。因此还须要开一维维护这个关系,存储上一个打饭的人到当前这我的的相对距离。
综上,设置:
\(dp[i][st][k]\)为:当第\(1\)到第\(i-1\)我的所有吃完饭后,\(i\)后\(7\)我的(把\(i\)也算上)吃没吃饭,\(i\)前面吃饭的人和\(i\)之间相对距离为\(k\)时的最小价值。(设\(0\)为没吃过,\(1\)为吃过)
那么答案就应该是\(\min\{dp[n+1][0][k]\}\quad (k\in[0,8])\)。这里的\(k\)的取值是由于数组不能开负数,因此把整个\(k\)的区间从\([-8,0]\)挪到\([0,8]\)了。
而后咱们考虑怎么去转移。
(注意:这里的状态(是以十进制存储的)拆成二进制(有8位)后,最后面那位表示的不是最后的那我的,偏偏相反,是第一我的(即当前的那个\(i\)))
能够想到的是,对于每个人,状态转移首先须要看这我的到底吃没吃,那么咱们分两种状况讨论:
第一种:这个\(i\)已经吃了。这种状况下,\(st\&1\)应该为真。那么就能够直接去推下一我的,转移方程为:
\[ dp[i+1][j>>1][k+7]=\min(dp[i+1][j>>1][k+7],dp[i][j][k+8]); \]
第二种:这个\(i\)尚未吃。这种状况下,是没有办法转移到\(i+1\)的,由于咱们的状态设置的是前面的人都已经打完饭了(以排除后效性)。因此咱们就要枚举状态来选择后面的7我的(固然包括本身)谁先打饭。
可是,这里须要细考虑一下:不是后面的7我的中的全部人都是能够打饭的。就好比后面的第\(7\)我的,若是后面的第一我的的忍耐度很小,一点都不能容忍本身后面的人先打,那么这个第\(7\)人就是选不了的。(没办法摊上暴躁同窗就这样)
因此咱们还须要在枚举的同时判断是否合法。
开一个变量\(limit\)储存目前可行的最大范围,随着枚举的继续,这个范围要么不动,要么缩小。因此若是当前枚举到的人超过了这个范围,那么他以后的全部人也都超出了这个范围,直接\(break\)掉就好。
这个时候的转移方程是:
\[ dp[i][j|(1<<h)][h+8]=\min(dp[i][j|(1<<h)][h+8],dp[i][j][k+8]+val[now]) \]
其中\(val[now]\)表示作这道菜的须要的时间。
(这里还有个性质:a|b-a&b==a^b)
因此就得出了完整的思路。
以及完整的代码:
#include<stdio.h> #include<string.h> #define INF 0x3f3f3f3f #define min(a,b) a<b?a:b int n,ans,limit; int t[1010],b[1010]; int dp[1010][1<<8][20]; //dp[i][st][k]表示当前为i人、其后状态为st、 //前一个吃饭的人离i距离为k时须要的最少时间。 //即上一个吃饭的人为i+k-8 char *p1,*p2,buf[100000]; #define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++) int read() { int x=0,f=1; char ch=nc(); while(ch<48){if(ch=='-')f=-1;ch=nc();} while(ch>47) x=(((x<<2)+x)<<1)+ch-48,ch=nc(); return x*f; } int main() { int T; T=read(); while(T--) { n=read(); for(int i=1;i<=n;i++) t[i]=read(),b[i]=read(); memset(dp,INF,sizeof(dp)); dp[1][0][7]=0; for(int i=1;i<=n;i++) for(int j=0;j<(1<<8);j++) for(int k=-8;k<=7;k++) if(dp[i][j][k+8]!=INF) { if(j&1) dp[i+1][j>>1][k+7]=min(dp[i+1][j>>1][k+7],dp[i][j][k+8]); else { limit=INF; for(int h=0;h<=7;h++) if(!((j>>h)&1)) { if(i+h>limit) break; limit=min(limit,i+h+b[i+h]); dp[i][j|(1<<h)][h+8]=min(dp[i][j|(1<<h)][h+8],dp[i][j][k+8]+(i+k?(t[i+k]^t[i+h]):0)); } } } ans=INF; for(int k=0;k<=8;k++) ans=min(ans,dp[n+1][0][k]); printf("%d\n",ans); } return 0; }