P2157 [SDOI2009]學校食堂
阿新 • • 發佈:2020-09-15
我的這個方法比較奇怪,我是用佇列來實現轉移的。
首先分析一下題目性質。如果\(x\)是打完飯的人當中排隊最靠後的,那麼1到x-8這些人肯定都打完飯了。定義狀態\(f_{i,j,k}\)表示標號最大的打到飯的是\(i\)(有點拗口),最後一個打飯的是\(i-k\),\(k\)的範圍是0到7,\(i-1\)到\(i-7\)的狀態是\(j\),需要的最少的時間。
這種狀態定義沒有後效性(想想為什麼),只是不好確定轉移的順序。核心操作來了,其實說白了就是bfs,我們可以用佇列存狀態,每次取隊首向外一層一層的擴充套件。這種方法還附帶剪枝的功能,跑的飛快。
程式碼實現比較繁瑣,注意要把不合法的狀態及時排除掉。
#include<bits/stdc++.h> using namespace std; typedef pair<int,int> pii; #define forg(i,x) for(int i=first[x];i;i=nxt[i]) #define uu unsigned #define fi first #define se second #define od(x) ((x)&1) #define ev(x) (od(x)^1) #define mi2(x) (1<<(x)) #define gw(x,j) ((x)>>(j)&1) //取出二進位制數的某一位 #define fre(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout) const int inf=0x3f3f3f3f; int n,t[1003],b[1003],dp[1003][8][1<<7],lim; int nn; struct dul{ int a,b,c; }q[2000003]; inline bool vad1(int i,int zi,int k){ //if(b[i-k]<k)return 0; for(int ii=k+1;ii<=7;++ii)if(!gw(zi,ii-1)&&b[i-ii]<ii-k)return 0; return 1; } inline bool vad2(int i,int zi,int k){ if(i+k>n)return 0; for(int ii=1;ii<=7;++ii)if(!gw(zi,ii-1)&&b[i-ii]<k+ii)return 0; for(int ii=1;ii<k;++ii)if(b[i+ii]<k-ii)return 0; return 1; } inline void pr2(int zi){for(int k=1;k<=7;++k)printf("%d",gw(zi,k-1));} int main(){ lim=(1<<7)-1; int cases;scanf("%d",&cases);while(cases--){ scanf("%d",&n);for(int i=1;i<=n;++i)scanf("%d%d",t+i,b+i); memset(dp,0x3f,sizeof(dp)); int qh=1,qt=0; q[++qt]=(dul){0,0,lim},dp[0][0][lim]=0; while(qh<=qt){ int i=q[qh].a,j=q[qh].b,zi=q[qh].c;++qh; for(int k=1;k<=7;++k)if(!gw(zi,k-1)&&vad1(i,zi,k)){ int &f=dp[i][k][zi|mi2(k-1)]; if(f==inf)q[++qt]=(dul){i,k,zi|mi2(k-1)}; f=min(f,i==0?0:dp[i][j][zi]+(t[i-j]^t[i-k])); } for(int k=1;k<=7;++k)if(vad2(i,zi,k)){ int zz=(zi<<k)|mi2(k-1);zz&=lim; int &f=dp[i+k][0][zz]; if(f==inf)q[++qt]=(dul){i+k,0,zz}; f=min(f,i==0?0:dp[i][j][zi]+(t[i-j]^t[i+k])); } } int ans=inf; for(int k=0;k<=7;++k)ans=min(ans,dp[n][k][lim]); printf("%d\n",ans); } return 0; }