这几天做的多校联合状态很不好,而且多校的题目数据也略坑爹(居然赛后知道有输出文件为空的数据。。。。)。。。
说说这几天做的dp题目吧。。。
dp感觉是越来越切不动了,各种状态不会想。。。
(一):Live Archive 3983
先说说这个Robotruck这题吧,题目还是很好的,状态还是比较好想的(但我就是没转移出来。。蒟蒻啊)
状态:dp[i] 表示第i个物品输送完毕并且回到了起点
Dis(i,j) 表示从起点出发送出从j+1到i这些物品并回到原点的时间
转移方程: dp[i]=dp[j]+Dis(j+1,i),其中 Weight[i]-Weight[j]<=C
根据这个方程,我们能得到一个O(n*C)复杂度的算法
核心代码如下:
for(int i=1;i<=n;++i){
int cur=w[i],tot=Dis(P[i].x,P[i].y,0,0);
for(int j=i-1;cur<=C&&j>=0;--j){
d[i]=min(d[i],d[j]+tot+Dis(P[j+1].x,P[j+1].y,0,0));
cur+=w[j];
tot+=Dis(P[j].x,P[j].y,P[j+1].x,P[j+1].y);
}
// cout<<"d["<<i<<"] "<<d[i]<<endl;
}
但是我们可以把这个dp方程改写一下:
dp[i]=dp[j]+Cal(i)-Cal(j)+Distance(i)+Distance(j)
其中Cal(i)表示从起点按顺序走到达i点的总距离,Distance(i)表示起点到i节点的距离
这样我们就发现可以用单调队列优化了,由于dp[j]-cal(j)+Distance(j)是变量,我们把它放入队列,每次维护一个最小值的队列即可,这样时间复杂度可以达到O(n),优化之后的程序rank直接跑到前五了
全部代码:
#define LIM 100010
int w[LIM];
int re[LIM];
struct qnode {
int index, value;
};
struct MyQueue {
int head, tail;
qnode q[LIM];
void init() {
head = tail = 0;
}
void pushmin(int x, int y) {
while (tail > head && q[tail - 1].value > y) {
tail--;
}
q[tail].index = x;
q[tail++].value = y;
}
void pop(int id,int cap) {
while ( head < tail && ( w[id] - w[q[head].index] + re[q[head].index] > cap )) {
head++;
}
}
int Head() {
return q[head].value;
}
};
MyQueue qmin;
struct node{
int x,y;
}P[LIM];
int d[LIM];
int Sum[LIM];
int Dis(int x1,int y1,int x2,int y2){
return Abs(x1-x2)+Abs(y1-y2);
}
int main()
{
int T;
scanf("%d",&T);
int Cnt=1;
while( T-- ){
int n,C;
scanf("%d%d",&C,&n);
int x,y,z;
d[0]=0;w[0]=0;P[0].x=0;P[0].y=0;re[0]=0;
Sum[0]=0;
for(int i=1;i<=n;++i){
scanf("%d%d%d",&x,&y,&z);
P[i].x=x;P[i].y=y;
re[i]=z;
w[i]=w[i-1]+z;
Sum[i]=Sum[i-1]+Dis(P[i-1].x,P[i-1].y,x,y);
}
qmin.init();
for(int i=1;i<=n;++i){
int tot=Dis(P[i].x,P[i].y,0,0);
qmin.pushmin(i,d[i-1]-Sum[i]+tot);
qmin.pop(i,C);
int t=qmin.Head();
d[i]=t+Sum[i]+tot;
// cout<<"d["<<i<<"] "<<d[i]<<endl;
}
// printf("Case %d: %d\n",Cnt,d[n]);
if( Cnt!=1 ) puts("");
printf("%d\n",d[n]);
Cnt++;
}
return 0;
}
(二):Live Archive4794 Sharing Chocolate
这道题是哈尔滨world final 题(好像是当时最水的一道题)
其实看到n<=15时,我们就可以想到状态压缩了,状态比较麻烦,我为了降维,状态
为dp[state][max(width,height)],state为是选了将要分的巧克力的压缩状态,后一维为高与宽的最大值,由于前面状态已经能表示巧克力小块的总数,所以这个状态可以唯一确定一个巧克力的状态。这个状态表示此巧克力能否分下去
状态转移:dp[cur][w]=(dp[sub_1][w]&&dp[cur-sub_1][w])||(dp[sub_1][h]&&dp[cur-sub_1][h])
其中h能由w算得,sub_1表示能切的一个子状态,cur-sub_1表示切了之后剩下的一个子状态。
具体代码:
#define LIM 32780
#define MAXN 110
int d[LIM][MAXN];
int val[20];
int Sum[LIM];
int n;
void init(int tot,int width){
memset(d,-1,sizeof(d));
}
int solve(int state,int width){
if( state==0 ) return 0;
if( d[state][width] != -1 ){
return d[state][width];
}
if( (state&(state-1)) == 0 ) return d[state][width]=1;
int sub=state&(state-1);
int hei=Sum[state]/width;
while( sub > 0 ){
int cur=(state^sub);
int ss=Sum[sub],sc=Sum[cur];
if( ss%width==0 && sc%width==0 ){
if( solve(sub,max(width,ss/width)) && solve(cur,max(width,sc/width)) ){
return d[state][width]=1;
}
}
if( ss%hei==0 && sc%hei==0 ){
if( solve(sub,max(hei,ss/hei)) && solve(cur,max(hei,sc/hei)) ){
return d[state][width]=1;
}
}
sub=state&(sub-1);
}
return d[state][width]=0;
}
int main()
{
int Cnt=1;
while( cin>>n ){
if( n==0 ) break;
int width,height;
cin>>width>>height;
int S=0;
for(int i=0;i<n;++i){
cin>>val[i];
S+=val[i];
}
cout<<"Case "<<Cnt++<<": ";
if( S != width*height ){
cout<<"No"<<endl;
continue;
}
int tot=(1<<n)-1;
init(tot,max(width,height));
for(int i=0;i<=tot;++i){
Sum[i]=0;
for(int j=0;j<n;++j){
if( i&(1<<j) ){
Sum[i]+=val[j];
}
}
}
if( solve(tot,max(width,height)) ){
cout<<"Yes"<<endl;
}
else{
cout<<"No"<<endl;
}
}
return 0;
}
(三):Live Archive 4394 String Painter
08年成都现场赛题目,开始想了好久,不太懂,其实一开始就想出了状态,但是在转移
上就卡死了,而且涉及两个串,就有点搞不清了,后来又看了大牛博客,开始懂了
首先应该对目标态进行分析,得出一些结论
具体见:http://hi.baidu.com/angellunamaria/blog/item/bef2ee34eb2a6146241f1472.html
#define LIM 110
string s1,s2;
int dp[LIM][LIM][30];
int f[LIM];
int solve(int s,int e,int col){
// cout<<endl<<"first "<<endl;
// cout<<"s "<<s<<" e "<<e<<" col "<<col<<endl;
if( s > e ) return 0;
if(dp[s][e][col]!=-1){
return dp[s][e][col];
}
int &ans=dp[s][e][col];
ans=INF;
if( col == 0 ){
for(int i=s;i<=e;++i){
if( s2[i] == s2[s] ){
ans=min(ans,solve(s,i,s2[s]-'a'+1)+1+solve(i+1,e,col));
}
}
}
else{
int l=s,r=e;
// if( s==4 && e==6 )
// cout<<"l "<<l<<" r "<<r<<endl;
bool flagl=false,flagr=false;
while( l <= e ){
if( s2[l] == col-1+'a' ){
l++;
}
else{
flagl=true;
}
if( s2[r] == col-1+'a' ){
r--;
}
else{
flagr=true;
}
if( flagl && flagr ){
break;
}
}
// if( s==4 && e==6)
// cout<<"l "<<l<<" r "<<r<<endl;
if( l > e ){
ans=0;
}
else{
for(int i=l;i<=r;++i){
if( s2[i] == s2[l] )
ans=min(ans,solve(l,i,s2[l]-'a'+1)+1+solve(i+1,r,col));
}
}
}
// cout<<"s "<<s<<" e "<<e<<" col "<<col<<endl;
// cout<<"ans "<<ans<<endl;
return ans;
}
int main()
{
while( cin>>s1>>s2 ){
int len=s1.size();
memset(dp,-1,sizeof(dp));
// cout<<solve(0,len-1,0)<<endl;
for(int i=0;i<len;++i){
for(int j=len-1;j>=i;--j){
solve(i,j,0);
// cout<<"dp["<<i<<"]["<<j<<"][0] "<<dp[i][j][0]<<endl;
}
}
for(int i=0;i<len;++i){
if( s1[i] == s2[i] ){
if( i==0 ) f[i]=0;
else f[i]=f[i-1];
}
else{
if( i==0 ) f[i]=1;
else{
f[i]=dp[0][i][0];
for(int j=0;j<i;++j){
f[i]=min(f[i],f[j]+dp[j+1][i][0]);
}
}
}
// cout<<"f["<<i<<"] "<<f[i]<<endl;
}
cout<<f[len-1]<<endl;
}
return 0;
}
(四):Live Archive 4327 Parade
这好像是08年北京现场赛题,其实状态不是很难想,很难搞定的是优化状态转移
dp[i][j]表示在第i第j个位置最大可能值为多少
状态方程dp[i[j]=dp[i+1][t]+Dis(j,t) (其中Len(j,t)<=K)
这个题的状态方程我们发现其实跟刚才讲的Robotruck这题差不多,这提示我们又可以用单调队列优化
分两个方向优化,从左到右与从右到左
#define LIM 10010
int Suml[LIM],Sump[LIM];
int N,M,K;
struct qnode {
int index, value;
};
struct MyQueue {
int head, tail;
qnode q[LIM];
void init() {
head = tail = 0;
}
void pushmax(int x, int y) {
while (tail > head && q[tail - 1].value < y) {
tail--;
}
q[tail].index = x;
q[tail++].value = y;
}
void pop(int u) {
while (head < tail && (Suml[u]-Suml[q[head].index] > K) ) {
head++;
}
}
int Head() {
return q[head].value;
}
};
MyQueue qmax;
#define MAXN 110
int a[MAXN][LIM];
int b[MAXN][LIM];
int c[LIM],d[LIM];
//int q[LIM];
void solve(int u){
for(int i=0;i<=M;++i){
d[i]=-INF;
}
Suml[0]=0;Sump[0]=0;
for(int i=1;i<=M;++i){
Suml[i]=Suml[i-1]+b[u][i];
Sump[i]=Sump[i-1]+a[u][i];
}
qmax.init();
for(int i=0;i<=M;++i){
qmax.pushmax(i,c[i]-Sump[i]);
qmax.pop(i);
int t=qmax.Head();
d[i]=max(d[i],Sump[i]+t);
}
qmax.init();
Suml[M]=0;Sump[M]=0;
for(int i=M-1;i>=0;--i){
Suml[i]=Suml[i+1]+b[u][i+1];
Sump[i]=Sump[i+1]+a[u][i+1];
}
for(int i=M;i>=0;--i){
qmax.pushmax(i,c[i]-Sump[i]);
qmax.pop(i);
int t=qmax.Head();
d[i]=max(d[i],Sump[i]+t);
}
for(int i=0;i<=M;++i){
c[i]=d[i];
}
}
int main()
{
while( scanf("%d%d%d",&N,&M,&K) ){
if( N==0 && M==0 && K==0 ) break;
N++;
for(int i=1;i<=N;++i){
for(int j=1;j<=M;++j){
// cin>>a[i][j];
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=N;++i){
for(int j=1;j<=M;++j){
// cin>>b[i][j];
scanf("%d",&b[i][j]);
}
}
memset(c,0,sizeof(c));
for(int i=N;i>=1;--i){
solve(i);
}
int ans=0;
for(int i=0;i<=M;++i) ans=max(ans,c[i]);
// cout<<ans<<endl;
printf("%d\n",ans);
}
return 0;
}
(五)UVa10817 Headmaster’s Headache
一个三进制状态压缩的dp题+01背包
其他不想说了,主要注意三进制状压的方法
#define LIM 66000
#define MAXN 110
int S,M,N;
int d[LIM];
int state[MAXN][10];
int w[MAXN];
int p[10];
int num[10];
int Target;
void pre(){
p[0]=1;
for(int i=1;i<=9;++i){
p[i]=p[i-1]*3;
}
}
void init(){
Target=0;
memset(state,0,sizeof(state));
int tot=p[S]-1;
for(int i=0;i<=tot;++i){
d[i]=INF;
}
}
void Change(int x){
int t=Target/p[x]%3;
if( t<2 ){
Target+=p[x];
}
}
void Change(int u,int x){
state[u][x]++;
if( state[u][x]>2 ){
state[u][x]=2;
}
}
void GetSub(int cost){
int tot=p[S]-1;
for(int i=0;i<S;++i){
num[i]=Target/p[i]%3;
}
for(int i=0;i<=tot;++i){
bool flag=true;
for(int j=0;j<S;++j){
int t=i/p[j]%3;
if( t > num[j] ){
flag=false;
break;
}
}
if( flag ){
d[i]=cost;
}
}
}
void solve(){
int tot=p[S]-1;
for(int i=0;i<N;++i){
for(int j=tot;j>=0;--j){
int tmp=0;
for(int k=0;k<S;++k){
int t=j/p[k]%3;
t=max(t-state[i][k],0);
tmp+=p[k]*t;
}
d[j]=min(d[j],d[tmp]+w[i]);
}
}
printf("%d\n",d[tot]);
}
int main()
{
char Str[100];
pre();
while( scanf("%d%d%d",&S,&M,&N)!=EOF ){
if( N==0 && M==0 && S==0 ) break;
int x;
int ans=0;
init();
for(int i=0;i<M;++i){
scanf("%d",&x);
ans+=x;
gets(Str);
int len=strlen(Str);
int pos=0;
while( (++pos) < len ){
sscanf(Str+pos,"%d",&x);
--x;
Change(x);
for(; pos<len && isdigit(Str[pos]);pos++)
;
}
}
for(int i=0;i<N;++i){
scanf("%d",&w[i]);
gets(Str);
int len=strlen(Str);
int pos=0;
while ((++pos) < len) {
sscanf(Str + pos, "%d", &x);
--x;
Change(i,x);
for (; pos < len && isdigit(Str[pos]); pos++)
;
}
}
GetSub(ans);
solve();
}
return 0;
}