20261002 DP专题

catfish

求若干个 \(3^{a_i}\)(\(a_i\) 互不相同)之和所得出的第 \(k\) 小的数。

前几个数从小到大依次为:\(1\),\(3\),\(4\),\(9\),\(10\),\(12\),\(13\),\(27\)......

  • 使用注意力可以得出,记 \(pos_t\) 为二进制下 \(k\) 第 \(t\) 个 \(1\) 从右往左的位数减一,\(cnt\) 为二进制下 \(k\) 的 \(1\) 的个数。则答案为 \(\sum_{i=1}^{cnt}3^{pos_i}\);
#include<bits/stdc++.h>
using namespace std;
long long pw3[50];
int tag[50];
int main(){
	freopen("catfish.in","r",stdin);
	freopen("catfish.out","w",stdout);
	int n;
	scanf("%d",&n);
	pw3[0]=1;
	for(int i=1;i<=32;i++) pw3[i]=pw3[i-1]*3;
	while(n--){
		long long k,ans=0;
		scanf("%lld",&k);
		int pos=0;
		while(k!=0) tag[++pos]=k%2,k/=2;
		for(int i=pos;i>=1;i--){
			int opt=i-1;
			if(tag[i]==1) ans+=pw3[opt];
		}
		printf("%lld\n",ans);
	}
	return 0;
}

meal

一个 \(01\) 矩阵,定义一个好的对角线为该矩阵中一个满足一条对角线全是 \(1\),其余部分全是 \(0\) 的子正方形的对角线。求这样的最长好对角线的长度。

注意到邪恶出题人卡空间卡到了 \(32MB\)。

  • 考虑暴力枚举对角线,如果做法比较优秀的话可以每个 \(1\) 只被遍历最多两次。这很简单,用行和列的前缀和稍加优化即可,我的代码就是这样写的;

  • 但是我们只有区区 \(32MB\) 的空间!所以要考虑进一步优化:一种是选择将二维前缀和数组压缩成一维的滚动数组,二是将前缀和去掉换成二分(尽管我不会);

非常悲惨,我并没有加空间优化,所以只能放上一份失败的代码(悲)。

#include<bits/stdc++.h>
using namespace std;
bool G[2503][2503],tag[2503][2503];
short sum1[2503][2503],sum2[2503][2503],dp[2503][2503],ans=0;
int main(){
	freopen("meal.in","r",stdin);
	freopen("meal.out","w",stdout);
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++) sum1[i][0]=0;
	for(int i=1;i<=m;i++) sum2[0][i]=0;
	memset(G,false,sizeof(G));
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			int opt;
			cin>>opt;
			if(opt==1) G[i][j]=true;
			else G[i][j]=false;
			sum1[i][j]=sum1[i][j-1]+(G[i][j]==true?1:0);
		}
	for(int j=1;j<=m;j++)
		for(int i=1;i<=n;i++) sum2[i][j]=sum2[i-1][j]+(G[i][j]==true?1:0);
	memset(dp,0,sizeof(dp));
	memset(tag,false,sizeof(tag));
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			if(G[i][j]==false) continue;
			if(tag[i][j]==true&&tag[i+1][j+1]==true) continue;
			dp[i][j]=1,tag[i][j]=true;
			int k=i+1,t=j+1;
			while(true){
				if(sum1[k][t-1]-sum1[k][j-1]!=0||sum2[k-1][t]-sum2[i-1][t]!=0) break;
				if(G[k][t]==false) break;
				dp[i][j]++,tag[k][t]=true;
				k++,t++;
			}
			ans=max(ans,dp[i][j]);
		}
	for(int i=1;i<=n;i++) sum1[i][n+1]=0;
	for(int i=1;i<=n;i++)
		for(int j=m;j>=1;j--) sum1[i][j]=sum1[i][j+1]+(G[i][j]==true?1:0);
	memset(dp,0,sizeof(dp));
	memset(tag,false,sizeof(tag));
	for(int i=1;i<=n;i++)
		for(int j=m;j>=1;j--){
			if(G[i][j]==false) continue;
			if(tag[i][j]==true&&tag[i+1][j-1]==true) continue;
			dp[i][j]=1,tag[i][j]=true;
			int k=i+1,t=j-1;
			while(true){
				if(sum1[k][t+1]-sum1[k][j+1]!=0||sum2[k-1][t]-sum2[i-1][t]!=0) break;
				if(G[k][t]==false) break;
				dp[i][j]++,tag[k][t]=true;
				k++,t--;
			}
			ans=max(ans,dp[i][j]);
		}
	cout<<ans;
	return 0;
}
/*
5 5
0 0 0 0 1
0 0 0 1 0
0 0 1 0 1
0 1 0 0 1
1 0 0 0 0
*/

经过实测,这份代码如果把数组开小点可以获得 \(50\) 分高分。

这是 \(std\) 代码:

#include<bits/stdc++.h>

using namespace std;

const int maxn=2510;
short n,m,ans,dp[maxn],up[maxn],r[maxn][maxn];
bool a[maxn][maxn];

int main(){
	freopen("meal.in","r",stdin);
	freopen("meal.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	while(cin>>n>>m){
		ans=0;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				cin>>a[i][j];
				r[i][j]=a[i][j]==1?0:r[i][j-1]+1;
			}
		}
		for(int i=1;i<=m;++i) dp[i]=a[1][i]?1:0;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j) up[j]=a[i][j]?0:up[j]+1;
			for(int j=m;j>=1;--j){
				ans=max(ans,dp[j]);
				if(a[i][j]){
					int ti=i+1,tj=j+1;
					if(ti>n||tj>m||!a[ti][tj]) continue;
					short k=min(up[tj],r[ti][tj-1]);
					k=min(k,dp[j]);
					dp[tj]=k+1;	
				}
				else dp[j+1]=a[i+1][j+1]?1:0;
			}
		}
		for(int i=1;i<=m;++i) up[i]=0,dp[i]=a[1][i]?1:0;
		for(int i=1;i<=n;++i){
			for(int j=m;j>=1;--j){
				r[i][j]=a[i][j]==1?0:r[i][j+1]+1;
			}
		}
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j) up[j]=a[i][j]?0:up[j]+1;
			for(int j=1;j<=m;++j){
				ans=max(ans,dp[j]);
				if(a[i][j]){
					int ti=i+1,tj=j-1;
					if(ti>n||tj<1||!a[ti][tj]) continue;
					short k=min(up[tj],r[ti][tj+1]);
					k=min(k,dp[j]);
					dp[tj]=k+1;	
				}
				else dp[j-1]=a[i+1][j-1]?1:0;
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

badnews

有根树,耗费以单位时间选择一个起始节点开始传播消息;任意一个节点可以消耗以单位时间向一个子节点或父节点传播消息。

求最少需要消耗的时间,以及所有可以满足时间最短的起始节点编号。

  • 首先可以发现有根树的限制就是滚木。因为可以向儿子或父亲传播,显然我们可以随意钦定一个起始节点作为根;

  • 对于一个节点 \(i\),假设其儿子 \(son_1\),\(son_2\),\(son_3\)......\(son_k\) 的最短时间均已求出,存在 \(ans\) 数组中,并且对于任意 \(i\) \(<\) \(j\) 都有 \(ans[son_i]\) \(<\) \(ans[son_j]\)。那么节点 \(i\) 的答案即为 \(max\{ans[son_i]+i\}\);

#include<bits/stdc++.h>
using namespace std;
vector<int> G[1003];
int ans[1003],last[1003];
bool cmp(int x,int y){return x>y;}
void work(int u,int father){
	int son[1003];
	int cnt=0;
	for(int v:G[u]){
		if(v==father) continue;
		work(v,u);
		son[++cnt]=ans[v];
	}
	sort(son+1,son+cnt+1,cmp);
	for(int i=1;i<=cnt;i++) ans[u]=max(ans[u],son[i]+i);
	return;
}
int main(){
	freopen("badnews.in","r",stdin);
	freopen("badnews.out","w",stdout);
	int n;
	while(cin>>n){
		int minans=1e8;
		for(int i=1;i<=n;i++) G[i].clear();
		for(int i=2;i<=n;i++){
			int fa;
			cin>>fa;
			G[i].push_back(fa),G[fa].push_back(i);
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++) ans[j]=0;
			work(i,0);
			last[i]=ans[i],minans=min(minans,last[i]);
		}
		cout<<minans+1<<"\n";
		for(int i=1;i<=n;i++)
			if(last[i]==minans) cout<<i<<" ";
		cout<<"\n";
	}
	return 0;
}

baffles

依旧是一个 \(01\) 矩阵,在行列之间插入一些挡板,使得分隔出来的部分均为矩形且每部分有且仅有一个 \(1\),求最少的隔板长度。

注意每部分均需要有 \(1\),不能只有 \(0\)。

  • 首先可以参考某年 \(NOI\) 的“棋盘分割”,得出一个 \(O(n^5)\) 的 \(dp\);

  • 但实际上,这个 \(dp\) 的转移和实现是建立在保证每一个隔板都将原矩阵分成两个部分,也就是“一刀切到底”的假设基础上的,所以只能过掉一部分数据;

  • 因而标程采用的算法是搜索。上文说过 \(dp\) 的结果尽管不是最优解,但是是一个相当接近最优解的上界。因此在搜索的时候,首先用DP给出一个上界,然后将这个值作为剪枝的条件可以大大加快出解的速度;

  • 搜索的大体思路是这样的:对每一个 \(1\),枚举仅仅包含这一个1的矩形,然后不停的搜下去,这个思路是比较简单的,也是不难实现的,如果没有以刚才的 \(dp\) 出的值作为上界的话,搜索的效率是很低的;

以上为大体思路,但是我实在是没有写。

遂下面给出标程:

#include <bits/stdc++.h>
using namespace std;

const int N = 32, M = 32;
const int INF = 1000000000;

int n, m, fishCount;
int f[N + 1][N + 1][M + 1][M + 1];
int sum[N + 1][M + 1];
unsigned int covered[N + 1], fullMask;
int best;

inline int Query(int x, int xx, int y, int yy)
{
	return sum[xx][yy] - sum[xx][y - 1] - sum[x - 1][yy] + sum[x - 1][y - 1];
}

inline unsigned int ColumnMask(int y, int yy)
{
	unsigned int rightMask = (yy == 32 ? 0xffffffffu : ((1u << yy) - 1));
	unsigned int leftMask = (y == 1 ? 0u : ((1u << (y - 1)) - 1));
	return rightMask ^ leftMask;
}
int GuillotineDP(int x, int xx, int y, int yy)
{
	if(Query(x, xx, y, yy) == 1) return 0;
	if(f[x][xx][y][yy] != -1) return f[x][xx][y][yy];

	int res = INF;
	for(int i = x; i < xx; ++i){
		if(Query(x, i, y, yy) > 0 && Query(i + 1, xx, y, yy) > 0){
			res = min(res, GuillotineDP(x, i, y, yy) + GuillotineDP(i + 1, xx, y, yy) + yy - y + 1);
		}
	}
	for(int i = y; i < yy; ++i){
		if(Query(x, xx, y, i) > 0 && Query(x, xx, i + 1, yy) > 0){
			res = min(res, GuillotineDP(x, xx, y, i) + GuillotineDP(x, xx, i + 1, yy) + xx - x + 1);
		}
	}
	return f[x][xx][y][yy] = res;
}
void Search(int cost, int coveredFish)
{
	if(cost + 2 * (fishCount - coveredFish) >= best + n + m) return;

	int x = 0, y = 0;
	for(int i = 1; i <= n; ++i){
		unsigned int rest = fullMask ^ covered[i];
		if(rest){
			x = i;
			y = __builtin_ctz(rest) + 1;
			break;
		}
	}

	if(x == 0){
		best = min(best, cost - n - m);
		return;
	}

	for(int ii = x; ii <= n; ++ii){
		int h = ii - x + 1;
		for(int jj = y; jj <= m; ++jj){
			int w = jj - y + 1;
			if(cost + h + w + 2 * (fishCount - coveredFish - 1) >= best + n + m) continue;
			if(Query(x, ii, y, jj) != 1) continue;

			unsigned int mask = ColumnMask(y, jj);
			bool ok = true;
			for(int k = x; k <= ii; ++k){
				if(covered[k] & mask){
					ok = false;
					break;
				}
			}
			if(!ok) continue;

			for(int k = x; k <= ii; ++k) covered[k] |= mask;
			Search(cost + h + w, coveredFish + 1);
			for(int k = x; k <= ii; ++k) covered[k] ^= mask;
		}
	}
}

void Solve()
{
	scanf("%d", &m);
	memset(sum, 0, sizeof(sum));
	memset(covered, 0, sizeof(covered));
	memset(f, -1, sizeof(f));

	fishCount = 0;
	for(int i = 1; i <= n; ++i){
		for(int j = 1; j <= m; ++j){
			int t; scanf("%d", &t);
			fishCount += t;
			sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + t;
		}
	}

	fullMask = (m == 32 ? 0xffffffffu : ((1u << m) - 1));

	if(fishCount <= 1){
		printf("0\n");
		return;
	}

	best = GuillotineDP(1, n, 1, m);
	Search(0, 0);
	printf("%d\n", best);
}

int main()
{
	freopen("baffles.in", "r", stdin);
	freopen("baffles.out", "w", stdout);

	while(~scanf("%d", &n)) Solve();

	fclose(stdin);
	fclose(stdout);
	return 0;
}
posted @ 2026-10-02 16:57  404-NoT_FinD  阅读(4)  评论(0)    收藏  举报