2021.03.24模拟赛DP

P4302字符串折叠

P4302字符串折叠
区间DP
f[i][j]表示i到j的最小表示
枚举区间[l,r],当循环节的长度是[r-l+1]的因数时可以循环,否则不能
因为在压缩后的字符串中一位数字也算一个字符,所以要分情况计算压缩后的字符串长度:

  • 当循环100次时,返回3(位数)+2(括号占2个字符)+f[l][l+k-1](循环节长度)
  • 当循环10~99次时,返回2(位数)+2(括号占2个字符)+f[l][l+k-1](循环节长度)
  • 当循环0~9次时,返回1(位数)+2(括号占2个字符)+f[l][l+k-1](循环节长度)

以上是折叠之后的情况
但是考虑到折叠后的长度(记为tmp)可能不如原长(f[j][j+i])更优,所以对tmp和f[j][j+i]取min
前面这些都记录好之后显然就有了状态转移方程

			for(int k=j;k<i+j;k++){
				f[j][j+i]=min(f[j][j+i],f[j][k]+f[k+1][j+i]);
			}

就找出来整个字符串的最小长度了
code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define maxn 110
using namespace std;

int len,tmp;
int f[maxn][maxn];//f[i][j]表示i到j的最小长度  注意,折叠之后可能反而比不折叠的还长 
char s[maxn];

int check(int l,int r,int k){
	for(int i=0;i<=r-l;i++){//暴力枚举因数,判断循环节的长度是不是满足在区间[l,r]内循环 
		if(s[l+i]!=s[l+(i%k)]) return 0x7fffffff;
	} 
	if((r-l+1)/k==100) return 3+2+f[l][l+k-1];
	if((10<=(r-l+1)/k)&&((r-l+1)/k<100)) return 2+2+f[l][l+k-1];
	if((0<=(r-l+1)/k)&&((r-l+1)/k<10)) return 1+2+f[l][l+k-1];
}

int main(){
	cin>>s+1;
	len=strlen(s+1);
//	cout<<len<<endl;//
	for(int i=0;i<len;i++){
		for(int j=1;i+j<=len;j++){
			f[j][j+i]=i+1;
			for(int k=1;k<=i+1;k++){//循环节的长度 
				if((i+1)%k==0){
					tmp=check(j,j+i,k);
					f[j][j+i]=min(f[j][j+i],tmp);
				}
			}
			for(int k=j;k<i+j;k++){
				f[j][j+i]=min(f[j][j+i],f[j][k]+f[k+1][j+i]);//状态转移方程 
			}
		}
	}
	cout<<f[1][len]<<endl;
	return 0;
}

P1941飞扬的小鸟

P1941飞扬的小鸟
一看就是DP,但是有好多疯狂的小细节啊QAQ
本质上是背包。上升是完全背包,因为它可以在单位时间内升好多次并且次数是整数;下降是01背包,因为在单位时间内最多降一次,只有降/不降两种选择
f[i][j]表示到达点(i,j)时最少要用的次数
上升的前一状态有两种,下降的前一状态也有两种,具体的见代码
统计答案的时候分开讨论:
先扫一遍最后一列上的点,如果有比0x3f3f3f3f小的数那太好了可以完成游戏,直接输出最小值就好了
要是没有就说明游戏失败了,那就倒着往前扫,看前面什么时候遇到符合条件的,OK记住这个点,再扫一遍所有的管道,看有多少个管道在这个点前面,得到答案
难道这就结束了吗?显然没有
疯狂的小细节:

  • 超出m,小鸟就不能再上升了,这时要判一下,在f[i][m]和f[i][j]之间取min(相当于鸟撞天花板上了直接压在这个高度了)
  • 不是管道的地方都是墙过不去,给这样的点赋个极大值就好了
  • 因为数据范围不大,所以把管道的最高点和最低点用up[]和down[]存一下,把它们转化成这个网格图上的点后面就很好用 一开始没想到就觉得很难写
  • 一点奇奇怪怪的东西:memset是按位赋值,二维数组每一位赋了0x3f之后f[i][j]就成了0x3f3f3f3f(别问我为啥会纠结在这个上qwq

code

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define inf 1061109567 
#define maxn 10010
#define maxm 2010
using namespace std;
template<typename T>
inline void read(T &x){
	x=0; bool flag=0; char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
	for(;isdigit(c);c=getchar()) x=x*10+(c^48);
	if(flag) x=-x;
}

int n,m,k,x[maxn],y[maxn];
int ans,tmp1,tmp2,cnt;
int f[maxn][maxm];//f[i][j] -> [i,j]
int up[maxn],down[maxn];
bool flag[maxn];
struct Pipe{
	int p;
	int l;
	int h;
}pip[maxn];

//bool cmp(Pipe a,Pipe b){
//	return a.p<b.p;
//}

void dp(){
	for(int i=1;i<=n;i++){
		for(int j=1+x[i];j<=m+x[i];j++){//升 
			f[i][j]=min(f[i][j-x[i]],f[i-1][j-x[i]])+1;
		}
		for(int j=m+1;j<=m+x[i];j++){//判最大值 
			f[i][m]=min(f[i][m],f[i][j]);
		}
		for(int j=1;j<=m-y[i];j++){//降
			f[i][j]=min(f[i][j],f[i-1][j+y[i]]);
		}
//		for(int j=1;j<=pip[i].l;j++) f[i][j]=0x3f;
//		for(int j=pip[i].h;j<=m;j++) f[i][j]=0x3f;
		for(int j=1;j<=down[i]-1;j++) f[i][j]=0x3f3f3f3f;
		for(int j=up[i]+1;j<=m;j++) f[i][j]=0x3f3f3f3f;
	}
//	cout<<"*"<<endl;
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=m;j++)
//			cout<<f[i][j]<<" ";
//			cout<<endl;
//	}
	for(int j=1;j<=m;j++) ans=min(ans,f[n][j]);
//	for(int j=1;j<=m;j++) cout<<"**"<<f[n][j]<<endl;
	
	if(ans>=0x3f3f3f3f) {
		cout<<0<<endl; 
		for(int i=n;i>=1;i--){//倒着搜找走的最长的 
			for(int j=1;j<=m;j++){
				tmp1=i;tmp2=j;
				if(f[i][j]<0x3f3f3f3f){
					
					break;
				}
			}
			if(tmp2<m) break;
		}
//		cout<<"tmp1="<<tmp1<<endl;
//		cout<<"tmp2="<<tmp2<<endl;
		for(int i=1;i<=k;i++){
			if(pip[i].p<=tmp1) cnt++;//注意是小于等于,不是小于,因为等于的时候也符合,能算进去一个QAQ 
		}
		cout<<cnt<<endl;
	}
	else {
		cout<<1<<endl;
		cout<<ans<<endl;
	}
}

int main(){
//	freopen("P1941_8.in","r",stdin);
	read(n),read(m),read(k);
	for(int i=1;i<=n;i++) up[i]=m,down[i]=1;
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=m;i++) f[0][i]=0; 
	for(int i=1;i<=n;i++) read(x[i]),read(y[i]);
	for(int i=1;i<=k;i++) {
		read(pip[i].p),read(pip[i].l),read(pip[i].h);
		flag[pip[i].p]=1;
		up[pip[i].p]=pip[i].h-1;//管道最高点 
		down[pip[i].p]=pip[i].l+1;//管道最低点 
	}
	ans=0x3f3f3f3f;
//	cout<<"***"<<ans<<endl;
//	sort(pip+1,pip+k+1,cmp);
	dp();
//	cout<<"***"<<f[0][0]<<endl;
	return 0;
}

[NOIP2015四校联训day6]小奇的仓库

换根DP+位运算拆位
一道很好的树形DP可惜我不会
感谢巨佬wsy_jim 熹圜 Whisper_Rain

本质就是:刨去自己的子树,然后把外面的加进来的过程——Whisper_Rain

解释放在代码里了虽然也不知道有没有说清楚
code

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#define ll long long
#define maxn 100010
using namespace std;
template<typename T>
inline void read(T &x){
	x=0; bool flag=0; char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
	for(;isdigit(c);c=getchar()) x=x*10+(c^48);
	if(flag) x=-x;
}

ll n,m,a,b,c,k,ans;
ll g[maxn],other[16],f[maxn][16],h[maxn];//g应该开到maxn而不是16...惨痛教训 
struct node{
    int to;
    int nxt;
    int v;
}e[2*maxn];

void add(int u,int v,int x){
    e[++k].to=v;//点的编号从1开始,所以不用把h初始化为-1 
    e[k].nxt=h[u];
    e[k].v=x;
    h[u]=k;
}

void dfs1(int x,int fa){//计算这个节点到它的子树中所有点的距离之和 
    for(int i=h[x];i;i=e[i].nxt){
        ll t=e[i].to;
        if(t!=fa){
            dfs1(t,x);
            f[x][e[i].v%16]++;//记录i为起点%16==j的边的个数 
            g[x]+=g[t]+e[i].v/16;//因为xor在0~15内,影响不到>=16的,所以把它们放到一起 
            for(int j=0;j<16;j++){//在子树里找点更新 
                ll k=j+e[i].v;//********
                f[x][k%16]+=f[t][j];
                g[x]+=k/16*f[t][j];//重新算边权 
            }
        }
    }
}

void dfs2(int x,int fa){//计算这个点到它的子树外的所有点的距离之和 
    for(int i=h[x];i;i=e[i].nxt){
        ll t=e[i].to;
        if(t!=fa){
            ll tmp=g[x]-g[t];
            for(int j=0;j<16;j++){
                ll k=j+e[i].v;
                tmp-=k/16*f[t][j];
                other[k%16]=f[x][k%16]-f[t][j];//other[]存这个点除了它的子树之外%16==j的点的个数 
            }
            other[e[i].v%16]--;
            f[t][e[i].v%16]++;
            g[t]+=tmp;
            for(int j=0;j<16;j++){
                ll k=j+e[i].v;//********
                f[t][k%16]+=other[j];
                g[t]+=k/16*other[j];
            }
            dfs2(t,x);
        }
    }
}

int main(){
    read(n),read(m);
    for(int i=1;i<=n-1;i++){
        read(a),read(b),read(c);
        add(a,b,c);
        add(b,a,c);
    }
//    memset(h,-1,sizeof(h));
    dfs1(1,0);
    dfs2(1,0);
//    for(int i=1;i<=n;i++){// 
//    	for(int j=0;j<16;j++)//
//    	    cout<<" *"<<f[i][j];//
//    	cout<<endl;//
//    }//
    for(int i=1;i<=n;i++){
        ans=16*g[i];
        for(int j=0;j<16;j++) ans+=(j^m)*f[i][j];
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2021-04-03 17:23  DReamLion  阅读(60)  评论(0编辑  收藏  举报