• 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
    ",ans);
        }
        return 0;
    }
    
  • 相关阅读:
    Android打包key密码丢失找回
    Java 操作 elasticsearch 报错(1)
    Linux Hbase1.2.6 安装及使用(1)
    JAVA WEB 作用域之间的区别
    JSTL 与 EL
    HTML CSS 常用单词
    java
    CentOS 7 MySQL 5.7 主从设置
    VMware安装CentOS7后配置静态IP
    MySQL explain,type分析(转)
  • 原文地址:https://www.cnblogs.com/DReamLion/p/14586864.html
Copyright © 2020-2023  润新知