zoukankan      html  css  js  c++  java
  • CF 1107 题解

    A

    如果长度为 (2) 特判,否则将第一个数和剩下的数分开。

    B

    (B) 进制下正整数的数根等价于 (pmod {B-1}),注意这里 (0) 对应的答案是 (B-1)

    所以答案是 ((k-1)B+x)

    C

    双指针扫出所有相同的段,每段取前 (k) 大即可。

    D

    观察一下发现只需要枚举 (x|n),然后前缀和预处理+判断所有矩阵是不是全 (0/1) 矩阵即可。

    时间复杂度分析一波:先考虑行的贡献,枚举的每一个 (x|n) 都会贡献 (frac{n}{x}) ,那么相当于是 (sigma_1(n)),所以复杂度是 (O(sigma_1(n)^2))。这是可以分析出复杂度 (O(n^2)) 的:考虑将 (n) 的所有因子分类,对于 (>sqrt{n}) 的,只有 (O(1)) 个,所以复杂度贡献 (O(n));对于 (<sqrt{n}) 的,因为 (sqrt{n} imes sqrt{n} = n),所以他们的和顶多贡献总复杂度 (O(n)),所以 (sigma_1(n)) 的量级可以认为是 (O(n)) 的。

    E

    这种区间 dp 模型都没见过。。再看了两道

    CF 1132 F

    题目链接

    每次删除相同的连续段有一个性质:互不包含的删除可以任意调换相对顺序。所以我们对于一个区间可以钦定左端点是最后被删除的。

    这个题就可以设 (f_{l,r}) 表示删除区间 ([l,r]) 的代价,转移首先可以花 (1) 代价删除 (l),或者是枚举一个 (x),满足 (s_l = s_x),将 (l) 并入 (x),代价就是 (f_{l+1,x-1}+f_{x,r})

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 500+5;
    int f[MAXN][MAXN],n;
    char str[MAXN];
    // 删除操作 一定互相包含 不交的连通块顺序无关
    // 删除首先枚举 mid,那么如果str[l]==str[mid]先删除[l+1,mid-1]然后l和mid就并起来了
    
    int main(){
    	scanf("%d",&n);
    	scanf("%s",str+1);
    	CLR(f,0x3f);
    	FOR(i,1,n) f[i][i] = 1,f[i][i-1] = 0;
    	FOR(len,2,n){
    		FOR(l,1,n-len+1){
    			int r = l+len-1;
    			f[l][r] = std::min(f[l+1][r],f[l][r-1])+1;
    			FOR(k,l+1,r){
    				if(str[k] == str[l]){
    					f[l][r] = std::min(f[l][r],f[l+1][k-1]+f[k][r]);
    				}
    			}
    		}
    	}
    	printf("%d
    ",f[1][n]);
    	return 0;
    }
    

    SPOJ ZUMA

    题目链接

    这个题我们需要知道连续段的长度了,还是用左端点最后删除的思路,设 (f_{l,r,k}) 表示区间 ([l,r])(l) 前面删除导致接了 (k) 个和 (s_l) 相同的数的答案。转移的时候首先可以加一个数在左端点左边,(f_{l,r,k} = f_{l,r,k+1}+1),然后如果现在可以消除了(也就是 (k-1 == K)),那就可以消除,也就是 (f_{l,r,K} = f_{l+1,r,0})。然后就是枚举和位置 (x) 拼在一起,(f_{l,r,k} = f_{l+1,x-1,0}+f_{x,r,min{k+1,K-1}})

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 100+5;
    int n,K,a[MAXN];
    int f[MAXN][MAXN][6];
    
    inline void upmin(int &x,int y){
    	if(x > y) x = y;
    }
    
    int main(){
    	scanf("%d%d",&n,&K);
    	FOR(i,1,n) scanf("%d",a+i);
    	CLR(f,0x3f);
    	FOR(i,1,n) FOR(j,0,K-1) f[i][i][j] = K-j-1;
    	FOR(i,1,n) f[i][i-1][0] = 0;
    	FOR(len,2,n){
    		FOR(l,1,n-len+1){
    			int r = l+len-1;
    			ROF(k,K-1,0){
    				upmin(f[l][r][k],f[l][r][std::min(K-1,k+1)]+1);
    				if(k == K-1) upmin(f[l][r][k],f[l+1][r][0]);
    				FOR(x,l+1,r){
    					if(a[l] == a[x]) upmin(f[l][r][k],f[l+1][x-1][0]+f[x][r][std::min(K-1,k+1)]);
    				}
    			}
    		}
    	}
    	printf("%d
    ",f[1][n][0]);
    	return 0;
    }
    /*
    每次消除连续段的 如果要保留连续段消息 就要设f[i][j][k] 表示区间[i,j],i前面接了长度为k的一段和i相同的还未删除的数的方案数 然后枚举消去
    转移 首先可以加入一个: f[i][j][k] = f[i][j][k+1]+1
    然后可以消去k个: f[i][j][k] = f[i+1][j][0]
    然后可以和后面的合并(如果相等): f[i][j][k] = f[i+1][j][k+1]
    */
    

    CF 1107 E

    首先我们先求出 (g_i) 表示将长度为 (i) 的区间消光的答案,这个可以用背包解决。

    然后,我们这个题也是要知道长度才能计算贡献,所以设 (f_{l,r,k}) 表示 ([l,r]) 前面跟着长度 (k) 的答案,转移的时候可以先删掉这一段,(f_{l,r,k} = f_{l+1,r,0}+g_{k+1}),也可以枚举下一个位置,和上一个题一样转移。

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 100+5;
    
    int n;
    char str[MAXN];
    LL a[MAXN];int b[MAXN];
    LL f[MAXN][MAXN][MAXN];// 区间[l,r],l接了长度为k的一段
    
    inline void upmax(LL &x,LL y){
    	if(x < y) x = y;
    }
    
    int main(){
    	scanf("%d",&n);
    	scanf("%s",str+1);
    	FOR(i,1,n) scanf("%lld",b+i);
    	FOR(i,1,n){
    		FOR(j,0,i-1){
    			upmax(a[i],a[j]+b[i-j]);
    		}
    	}
    	CLR(f,~0x3f);
    	FOR(i,1,n){
    		FOR(j,0,n-1) f[i][i][j] = a[j+1];
    		f[i][i-1][0] = 0;
    	}
    	FOR(len,2,n){
    		FOR(l,1,n-len+1){
    			int r = l+len-1;
    			FOR(k,0,n){
    				upmax(f[l][r][k],f[l+1][r][0]+a[k+1]);
    				FOR(x,l+1,r){
    					if(str[x] == str[l]) upmax(f[l][r][k],f[l+1][x-1][0]+f[x][r][k+1]);
    				}
    			}
    		}
    	}
    	printf("%lld
    ",f[1][n][0]);
    	return 0;
    }
    /*
    区间连续段+删除相关dp可以记左端点前面的个数
    转移 枚举一个str[l]==str[x]
    f[l][r][k] = f[l+1][x-1][0]+f[x][r][k+1]
    f[l][r][k] = f[l][r][0]+a[k+1]
    */
    

    总结:区间 dp 如果有时候可以不注意操作顺序,就可以定义最有利于转移的顺序(这种模型中就是最后删除左端点),然后记录与其相关的值(最左边延长了多少)。这种删除模型的核心是删掉了后会接起来,所以记录端点往左延伸多少也没错。

    另一个区间dp:每次可以选择一段区间染色,求最少到目标状态的步数。

    先考虑染色操作有时候不能分成两个区间的原因是可能会跨段染色,于是我们可以

    (f_{l,r}) 表示 ([l,r]) 的答案,每次转移的时候如果 (c_l = c_r) 那么我们可以用 (f_{l+1,r}) 染完之后顺便把 (l) 染了,也就是 (f_{l,r}) 可以用 (f_{l+1,r},f_{l,r-1}) 更新。

    如果不相等,说明这两个点属于两次不同的染色,然后就可以枚举断点更新了。

    F

    直接正着做不太好处理每个位置前面对自己的贡献(状态会爆炸),我们倒着做,反而考虑每个位置对别人的贡献。首先贷款肯定是紧密连接的(要不然不优)。设 (f_{i,j}) 表示考虑到第 (i) 个贷款,当前选了 (j) 个的方案数,转移首先可以不选这个点,(f_{i,j} o f_{i+1,j}),或者可以选这个点,(f_{i,j} o f_{i+1,j+1}+a_i-b_i imesmin(k_i,j))

    但是发现这样 dp 的顺序不对,按照我们的方法我们应该贪心地让 (b_i) 大的先被考虑。

    但是发现还是不对(这样写你应该会发现第二个样例答案少了 (1)),发现原因是有些小的数已经覆盖了超过最右边的点,所以可以将这个点往前拿减少这个点的贡献,并且后面的点由于没有覆盖超,贡献是不变的。这种情况我们直接把后面的点全都一直往后拿就行,具体要加个转移 (f_{i,j} o f_{i+1,j} + a_i-b_ik_i)

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 500+5;
    
    struct Node{
    	LL a,b,k;
    	
    	inline bool operator < (const Node &t) const {
    		return b > t.b;
    	}
    }v[MAXN];
    int n;
    LL f[MAXN][MAXN];
    // i 个,选了j个
    
    int main(){
    	scanf("%d",&n);
    	FOR(i,1,n) scanf("%lld%lld%lld",&v[i].a,&v[i].b,&v[i].k);
    	std::sort(v+1,v+n+1);
    	CLR(f,-0x3f);
    	f[0][0] = 0;
    	FOR(i,1,n){
    		FOR(j,0,i){
    			f[i][j] = f[i-1][j];
    			f[i][j] = std::max(f[i][j],f[i-1][j]+v[i].a-v[i].k*v[i].b);
    			if(j-1 >= 0) f[i][j] = std::max(f[i][j],f[i-1][j-1]+v[i].a-std::min((LL)(j-1),v[i].k)*v[i].b);
    		}
    		// DEBUG(f[i][0]);
    	}
    	LL ans = -1e18;
    	FOR(i,0,n) ans = std::max(ans,f[n][i]);
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    G

    感觉难度倒序啊。。。

    这个题首先枚举右端点,然后考虑计算所有左端点的答案,发现那个 (max) 固定 (r) 后是个后缀和的形式,所以用单调栈维护一下所有后缀最大值,算一下贡献插入 multiset ,没了。

    如果想做到 (O(n)) 的话,首先 ST 表那一部分可以用每次暴力扫,删除栈顶的时候直接获得栈顶的最小值贡献来优化掉。

    multiset 那一部分其实可以对栈记录一个前缀最小值,这样每次更新就好了。(主要还是为了 vp 的时候避免细节快点写用了 (log)

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 3e5+ 5;
    const int MAXM = 18;
    
    struct Node{
    	LL val,ps,gx;
    	Node(LL val=0,LL ps=0,LL gx=0) : val(val),ps(ps),gx(gx) {}
    };
    
    int n,a;
    int d[MAXN];
    LL c[MAXN];
    Node st[MAXN];
    int tp;
    LL mn[MAXM+1][MAXN];// 求最小值
    int pc[MAXN];
    
    inline LL calc(int l,int r){
    	int c = pc[r-l+1];
    	l = std::max(l,0);r = std::min(r,n);
    	return std::min(mn[c][l],mn[c][r-(1<<c)+1]);
    }
    
    std::multiset<LL> S;
    
    int main(){
    	pc[0] = -1;FOR(i,1,MAXN-1) pc[i] = pc[i>>1]+1;
    	scanf("%d%d",&n,&a);
    	FOR(i,1,n) scanf("%d%lld",d+i,c+i);
    	FOR(i,1,n) c[i] = a-c[i],c[i] += c[i-1];
    	FOR(i,1,n) mn[0][i] = c[i];
    	FOR(i,1,MAXM){
    		for(int j = 0;j+(1<<(i-1)) < MAXN;++j){
    			mn[i][j] = std::min(mn[i-1][j],mn[i-1][j+(1<<(i-1))]);
    		}
    	}
    	LL ans = 0;
    	FOR(r,1,n){
    		ans = std::max(ans,c[r]-c[r-1]);
    		if(r != 1){
    			LL now = 1ll*(d[r]-d[r-1])*(d[r]-d[r-1]);
    			while(tp && st[tp].val <= now){
    				S.erase(S.find(st[tp].gx));
    				tp--;
    			}
    			st[tp+1] = Node(now,r,calc(st[tp].ps-1,r-1)+now);
    			++tp;S.insert(st[tp].gx);
    		}
    		if(!S.empty()) ans = std::max(ans,c[r]-*S.begin());
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    java实现第七届蓝桥杯平方圈怪
    Java三大器之过滤器(Filter)的工作原理和代码演示
    spring mvc 防止重复提交表单的两种方法,推荐第二种
    防止订单重复提交
    Swagger入门教程
    BigDecimal的用法详解(保留两位小数,四舍五入,数字格式化,科学计数法转数字,数字里的逗号处理)
    很认真的聊一聊程序员的自我修养
    JAVA利用反射映射JSON对象为JavaBean
    Eclipse导出JavaDoc(并解决中文乱码问题)
    Eclipse注释模板设置详解
  • 原文地址:https://www.cnblogs.com/rainair/p/14305567.html
Copyright © 2011-2022 走看看