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;
}

浙公网安备 33010602011771号