24.3.31周报(板刷dp,状压dp板子,二分kk)

星期一:

白天做了俩道区间dp

其一:    洛谷传送门

思路:dp【i】【j】表示最左山为i,最右山为j的不对称性,由dp【i+1】【j-1】转化而来,先枚举区间长度,再枚举左端点

代码如下:

ll n;
int h[5050];
ll dp[5050][5050],ans[5050];
void solve(){
	cin >> n;
	memset(dp,0x3f,sizeof dp);
	for(int i=2;i<=n;i++) ans[i]=1e9;
	for(int i=1;i<=n;i++){
		cin >> h[i];
		dp[i][i]=0;
	}
	for(int i=1;i<n;i++)
		dp[i][i+1]=abs(h[i+1]-h[i]),ans[2]=min(dp[i][i+1],ans[2]);
	for(int len=3;len<=n;len++){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1;
			dp[l][r]=min(dp[l+1][r-1]+abs(h[l]-h[r]),dp[l][r]);
			ans[len]=min(dp[l][r],ans[len]);
		}
	}
	for(int i=1;i<=n;i++) cout << ans[i] << " ";
}

其二:                        洛谷传送门

思路:和上题很像,dp【i】【j】表示还剩 i到j零食时最多的钱,由dp【i-1】【j】或dp【i】【j+1】转化而来

代码如下:

ll n;
int v[2020],dp[2020][2020];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> v[i];
	for(int len=n-1;len>=1;len--){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1,a=n-len;
			dp[l][r]=max({dp[l-1][r]+v[l-1]*a,dp[l][r+1]+v[r+1]*a,dp[l][r]});
		}
	}
	ll ans=0;
	for(int i=1;i<=n;i++) ans=max(1ll*dp[i][i]+v[i]*n,ans);
	cout << ans;
}

下午补了道成信大校赛格温的剪刀:            pta传送门

思路:二分+kk算法,和cf round 936的C有点像,开始可能会想成贪心,但贪心并不能得到最优解,于是得二分跑kk,边按照happy度排序,如果beaty度小于二分的值就跳过

代码如下:

const int N=2e6+10;
const int mod=1e9+7;
ll n;
int m;
struct nod{
	int u,v,bea,hap;
}e[N];
int fa[N];
int fnd(int x){
	return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
ll check(ll x){
	for(int i=1;i<=n;i++) fa[i]=i;
	int cnt=0;ll sum=0;
	for(int i=1;i<=m;i++){
		if(e[i].bea<x) continue;
		int u=fnd(e[i].u),v=fnd(e[i].v);
		if(u==v) continue;
		fa[v]=u;
		cnt++;
		sum+=e[i].hap;
		if(cnt==n-1) return sum;
	}
	return 0;
}
bool cmp1(nod a,nod b){
	return a.hap<b.hap;
}
void solve(){
	cin >> n >> m;
	for(int i=1;i<=m;i++)
		cin >> e[i].u >> e[i].v >> e[i].bea >> e[i].hap;
	sort(e+1,e+m+1,cmp1);
	ll ans1=0,ans2=0;
	ll l=1,r=INT_MAX;
	while(l<=r){
		ll mid=l+r>>1;
		if(ll tmp=check(mid)) ans1=mid,ans2=tmp,l=mid+1;
		else r=mid-1;
	}
	cout << ans1 << "\n" << ans2;
}

星期二:

上午做了道高精度like题,还有环形石子合并       洛谷传送门

思路:很典的化环为链,和线性石子合并基本一样

天体训练赛,遇到了道非常恶心的字符串题,没碰

还有一道倒着用并查集的题,不过我没想到,因为数据范围小,所以我正着每次操作后都跑了一遍图,倒着用并查集的trick要加深印象

区间dp  涂色                           洛谷传送门

思路:dp【i】【j】表示将 i 到 j 涂好的操作数,考虑可以从什么状态转移过来

做了前两道区间dp我都忘了还能有第三重循环,dp【l】【r】可以从dp【l】【k】+dp【k+1】【r】转移过来,所以记得枚举l和r间的分界点,还有两个特判

代码如下:

ll n;
string s;
int dp[55][55];
void solve(){
	cin >> s;
	n=s.size(),s=" "+s;
	memset(dp,0x3f,sizeof dp);
	for(int i=1;i<=n;i++) dp[i][i]=1;
	for(int len=2;len<=n;len++){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1;
			if(s[l]==s[l+1] || s[l]==s[r]) dp[l][r]=min(dp[l+1][r],dp[l][r]);
			if(s[r]==s[r-1] || s[r]==s[l]) dp[l][r]=min(dp[l][r-1],dp[l][r]);
			for(int k=l;k<r;k++)
				dp[l][r]=min(dp[l][k]+dp[k+1][r],dp[l][r]);
		}
	}
	cout << dp[1][n];
}

星期三:

补了道集美大学15届校赛的背包问题:             牛客传送门

思路:因为总重量过大,不能跑常规的01背包,所以这里记dp【i】为装入 i 价值物品所需最小重量,状态转移为dp【i】=min(dp【i-v】+w,dp【i】),然后再跑遍dp数组确保其单调不减性,就可以对每次询问进行二分查询了

代码如下:

const int N=1e4+10;
const int mod=1e9+7;
ll n;
ll dp[N];
void solve(){
	cin >> n;
	int sumv=0;
	memset(dp,0x3f,sizeof dp);           //初始化
	dp[0]=0;
	for(int i=1;i<=n;i++){
		int w,v; cin >> w >> v;
		sumv+=v;
		for(int j=sumv;j>=v;j--)
			dp[j]=min(dp[j-v]+w,dp[j]);
	}
	for(int i=sumv-1;i;i--) if(dp[i]>dp[i+1]) dp[i]=dp[i+1];
	int q; cin >> q;
	while(q--){
		ll w; cin >> w;
		int l=1,r=sumv,res=0;
		while(l<=r){
			int mid=l+r>>1;
			if(dp[mid]<=w) res=mid,l=mid+1;
			else r=mid-1;
		}
		cout << res << "\n";
	}
}

补道中传校赛的树题:

思路:对每个节点进行质因数分解,从根节点dfs,因子数量不够就往上传,够了就直接return 1,存质因数及其指数,用map来实现,最开始用的vector,难写及写拉了没过,map一下就过了

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
ll k;
int p[N],cnt;
bool vi[N];
vector<int>ve[N];
map<ll,ll>pi[N];                       //二维map
ll ans;
void getp(int x){
	for(int i=2;i<=x;i++){
		if(!vi[i]) p[++cnt]=i;
		for(int j=1;1ll*i*p[j]<=x;j++){
			vi[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}
void calp(int x){
	int y=x;
	for(int i=1;p[i]<=x/p[i] && x!=1;i++){
		int tmp=0;
		while(x%p[i]==0){
			x/=p[i];
			tmp++;
		}
		if(tmp) pi[y][p[i]]+=tmp;
	}
	if(x!=1) pi[y][x]+=1;
}
void meg(int x,int y){
	for(auto [a,b]:pi[y]) pi[x][a]+=b;
}
bool dfs(int x,int f){
	bool if1=0;
	for(auto i:ve[x]){
		if(i==f) continue;
		if(dfs(i,x)) if1=1;
		else meg(x,i);
	}
	if(if1){ans++; return 1;}
	ll sum=1;
	for(auto [a,b]:pi[x]){
		sum*=b+1;
		if(sum>=k){ans++; return 1;}
	}
	return 0;
}
void solve(){
	cin >> n >> k;
	getp(n);
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
		if(pi[u].empty()) calp(u);
		if(pi[v].empty()) calp(v);
	}
	dfs(1,0);
	cout << ans << "\n";
}

星期四:

牢王拉了套dp专题题单,开始板刷dp!

线性dp第一题:拼字符串                            cf传送门

思路:dp【i】【j】表示首字母为i,尾字母为j的最长字符串长度

转移方程:对于字符串abc,

在dp【i】【a】非0的情况下,dp【i】【c】=max(dp【i】【a】+ 3 ,dp【i】【c】)

代码如下:

ll n;
int dp[30][30];
int num(char c){
	return c-'a'+1;
}
void solve(){
	cin >> n;
	for(int ii=1;ii<=n;ii++){
		string s; cin >> s;
		for(int i=1;i<=26;i++){
			if(!dp[i][num(s[0])]) continue;
			dp[i][num(s.back())]=max(dp[i][num(s[0])]+(int)s.size(),dp[i][num(s.back())]);
		}
		
        dp[num(s[0])][num(s.back())]=max((int)s.size(),dp[num(s[0])][num(s.back())]);
        //这句放循环前面可能会导致长度多加一次
	}
	ll ans=0;
	for(int i=1;i<=26;i++) ans=max(1ll*dp[i][i],ans);
	cout << ans;
}

背包(线性)dp第一题:撕缎带             cf传送门

思路:dp【i】表示考虑到 i 长度最多段数

          开始写了个从n到1的dfs,不出意外的t了,其实是线性dp,赋初值后,后续的值只能从非0的状态转移

代码如下:

ll n;
int a,b,c;
ll dp[4040];
void solve(){
	cin >> n >> a >> b >> c;
	dp[a]=dp[b]=dp[c]=1;
	for(int i=min({a,b,c});i<=n;i++){
		if(i>a && dp[i-a]) dp[i]=max(dp[i-a]+1,dp[i]);
		if(i>b && dp[i-b]) dp[i]=max(dp[i-b]+1,dp[i]);
		if(i>c && dp[i-c]) dp[i]=max(dp[i-c]+1,dp[i]);
	}
	cout << dp[n];
}

区间dp第一题:祖玛                cf传送门

思路:dp【i】【j】表示消除 i 到 j 的最少时间

先枚举区间长度,再枚举左端点,再枚举左右分界点,这个是常规操作

还有记得特判消除后剩下的珠子会再聚集的情况

代码如下:

ll n;
int c[550];
ll dp[550][550];
void solve(){
	cin >> n;
	memset(dp,0x3f,sizeof dp);
	for(int i=1;i<=n;i++){
		cin >> c[i];
		dp[i][i]=1;
		if(c[i]==c[i-1]) dp[i-1][i]=1;
	}
	for(int len=2;len<=n;len++){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1;
			if(c[l]==c[r]) dp[l][r]=min(dp[l+1][r-1],dp[l][r]);
			if(r+len-1<=n && c[l]==c[r+len-1])
                dp[l][r+len-1]=min(dp[l+1][r-1]+dp[r][r+len-2],dp[l][r+len-1]);
			if(l-len+1>=1 && c[l-len+1]==c[r])
                dp[l-len+1][r]=min(dp[l-len+2][l]+dp[l+1][r-1],dp[l-len+1][r]);
			for(int k=l;k<r;k++)
				dp[l][r]=min(dp[l][k]+dp[k+1][r],dp[l][r]);
		}
	}
	cout << dp[1][n];
}

补牛客寒假集训营2一题:             牛客传送门

思路:暴搜

代码如下:

ll n;
string s;
ll y,ans;
set<char>st;
void dfs(string s,int pos){
	if(pos==3){
		ll num=0;
		for(int i=2;i>=0;i--)
			num=num*10+s[i]-'0';
		if(num%8) return ;
	}
	if(pos==n){
		if(s.back()=='0' && n>1) return ;
		ll num=0;
		for(int i=n-1;i>=0;i--)
			num=num*10+s[i]-'0';
		if(num>y || num%8) return ;
		ans++,ans%=mod; return ;
	}
	if(s[pos]>='0' && s[pos]<='9'){
		dfs(s,pos+1);
	}else if(s[pos]=='_'){
		for(s[pos]='0';s[pos]<='9';s[pos]++)
			dfs(s,pos+1);
		s[pos]='_';
	}else{
		for(int i='0';i<='9';i++){
			if(st.count(i)) continue;
			st.insert(i);
			auto t=s;
			for(auto &j:t)
				if(j==s[pos]) j=i;
			dfs(t,pos+1);
			st.erase(i);
		}
	}
}
void solve(){
	cin >> n >> s >> y;
	reverse(s.begin(),s.end());
	dfs(s,0);
	cout << ans << "\n";
	ans=0;
	st.clear();
}

星期五:

线性dp第二题:消数拿分           cf传送门

思路:看到数据范围,明显用桶装,对于每个数考虑拿不拿,拿了就从dp【i-2】转移,不拿就从dp【i-1】转移

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int bu[N];
ll dp[N];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		int x; cin >> x;
		bu[x]++;
	}
	for(int i=1;i<=1e5;i++)
		dp[i]=max(dp[i-2]+1ll*bu[i]*i,dp[i-1]);
	cout << dp[100000];
}

状压dp第一题:点菜           cf传送门

一度让我怀疑我到底会不会状压dp(答案是确实不会)

思路:dp【mask】【i】,mask表示压缩后的点菜状态,i表示最后点的一道菜

先枚举所有点菜的状态,再对每个状态枚举rule的x和y进行转移,复杂度O(2^n * n^2)

代码如下:

ll n;
int m,k;
ll dp[1<<19][20];
ll a[20],sa[20][20];
ll ans;
void solve(){
	cin >> n >> m >> k;
	for(int i=1;i<=n;i++) cin >> a[i];
	while(k--){
		int x,y,c; cin >> x >> y >> c;
		sa[x][y]=c;
	}
	for(int i=0;i<n;i++) dp[1<<i][i+1]=a[i+1];
	for(int mask=0;mask<1<<n;mask++){
		for(int i=0;i<n;i++){
			if(!(mask&1<<i)) continue;
			for(int j=0;j<n;j++){
				if(mask&1<<j) continue;
				int nmask=mask|(1<<j);
				dp[nmask][j+1]=max(dp[mask][i+1]+sa[i+1][j+1]+a[j+1],dp[nmask][j+1]);
			}
		}
	}
	for(int mask=0;mask<1<<n;mask++)
		if(__builtin_popcount(mask)==m)
			for(int i=1;i<=n;i++)
				ans=max(dp[mask][i],ans);
	cout << ans;
}

星期六:

昨晚牛客练习赛的C              牛客传送门

思路:枚举所有包含k的区间,将其所有物品当作一个物品放入背包,这个过程用前缀和优化,

然后再跑个完全背包,能确保对于k的限制条件能满足

很巧妙一道题,可惜赛时没想出来

代码如下:

ll n;
int m,k;
ll w[2020],v[2020];
ll dp[550];
void solve(){
	cin >> n >> m >> k;
	for(int i=1;i<=n;i++){
		cin >> w[i] >> v[i];
		w[i]+=w[i-1],v[i]+=v[i-1];
	}
	vector<PII>ve;
	for(int i=1;i<=k;i++){
		for(int j=k;j<=n;j++){
			ll ww=w[j]-w[i-1],vv=v[j]-v[i-1];
			ve.push_back({ww,vv});
		}
	}
	for(auto [w,v]:ve)
		for(int j=w;j<=m;j++)
			dp[j]=max(dp[j-w]+v,dp[j]);
	for(int i=1;i<=m;i++) cout << dp[i] << " ";
}

下午天梯训练赛,无言

晚上cf div1+2,掉了点分

周日:

dp专题线性dp第三题 子数组数量:               cf传送门

思路:dp【i】【j】表示考虑到前 i 个数,长度为 j 的子数组个数

转移:dp【i】【j】=dp【i-1】【j】,如果a【i】%j==0,dp【i】【j】+=dp【i-1】【j-1】

对于每一个a,找出其所有因子,复杂度为O(n*\sqrt{a} )

但开二维数组空间明显不够,于是用滚动数组的思想,开一维就够了

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
ll dp[N];
void solve(){
	cin >> n;
	dp[0]=1;                                        //谜之初始化
	for(int i=1;i<=n;i++){
		int a; cin >> a;
		vector<int>tmp;
		for(int j=1;j<=a/j;j++){
			if(a%j) continue;
			tmp.push_back(j);
			if(j!=a/j) tmp.push_back(a/j);
		}
		sort(tmp.begin(),tmp.end(),greater<int>());     //因子从大到小排序
		for(auto j:tmp)
			dp[j]+=dp[j-1],dp[j]%=mod;
	}
	ll ans=0;
	for(int i=1;i<=n;i++) ans+=dp[i],ans%=mod;
	cout << ans;
}

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值