ABC288 EFG 题解

E
注意到后面选对前面的答案没有影响,而且前面选的顺序对后面的影响是连续的一段(如选 2 个,那么对应的 \(c\) 就应该是 \(c[i-2..i]\)(对应 \(i\) 是 1、2、3 个选时的答案))
然后就可以 dp 了:设 \(dp[i][j]\) 表示考虑到前 \(i\) 个物品,选了 \(j\) 个时的最小花费,转移分两种情况:

  • 选了第 \(i\) 个:\(dp[i][j] \leftarrow dp[i-1][j-1]+a[i]+min{c[i-j+1], .., c[i]}\)
  • 没选(注意此时该物品不能在愿望单上) \(dp[i][j] \leftarrow dp[i-1][j]\)
    答案就是 \(min{dp[n][k..n]}\)
// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>
#define pb push_back

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 2e5+5;

int n,k,a[maxn],c[maxn],must[maxn];
ll dp[5005][5005];

signed main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)scanf("%d",&c[i]);
	for(int i=1,x;i<=k;i++){
		scanf("%d",&x);
		must[x] = 1;
	}
	
	for(int i=0;i<=n;i++)for(int j=0;j<=n;j++)dp[i][j] = 1e18;
	dp[0][0] = 0;
	for(int i=1;i<=n;i++){
		ll cst = 1e18;
		if(!must[i])dp[i][0] = dp[i-1][0];
		for(int j=1;j<=i;j++){
			cst = min(cst, 1ll * c[i - j + 1]);
			dp[i][j] = min(dp[i][j], dp[i-1][j-1] + cst + a[i]);
			if(!must[i])dp[i][j] = min(dp[i][j], dp[i-1][j]);
		}
	}
	ll ans = 1e18;
	for(int i=k;i<=n;i++)ans = min(ans, dp[n][i]);
	cout << ans << '\n';

	return 0;
}

F
\(dp[i]\) 表示考虑到第 \(i\) 位时的所有情况之和
\(dp[i] = \sum_{j=0}^{i-1}dp[j]\times x[j+1..i]\)
然而直接转移是 \(O(n^2)\) 的,需要优化
考虑将 \(x[j+1..i]\) 拆开 \(\rightarrow 10x[j+1..i-1] + x[i..i]\)
代入,\(dp[i] = \sum_{j=0}^{i-2}dp[j]\times 10x[j+1..i-1] + x[i..i]\sum_{j=0}^{i-1}dp[j]\)
发现拆开之后恰好能用 \(dp[i-1]\) 表示,即 \(dp[i] = 10dp[i-1] + x[i..i]\sum_{j=0}^{i-1}dp[j]\)
每次转移的时候维护一下 dp 的前缀和即可

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>
#define pb push_back

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, mod = 998244353;

int n;
char s[200005];
ll dp,sdp;
signed main(){
	scanf("%d",&n);
	scanf("%s",s +1);
	dp = s[1] - '0';sdp = dp+1;	// +1(dp[0]=1)
	for(int i=2;i<=n;i++){
		int c = s[i]-'0';
		dp = (dp*10%mod + sdp*c%mod) % mod;
		(sdp += dp) %= mod;
		
	}
	cout << dp << '\n';

	return 0;
}

G
注意到如果将三进制每一位拆开,题目中给的条件实际上相当于是一系列和
\(a[x]\)\(i\) 位为 0,说明至少有 \(a[x]\) 个数,这一位为 0,1 (加起来)
为 1,说明这一位为 0,1,2
为 2,则为 1,2
因此相当于是做一个高级的差分,具体细节等之后再补充一下
UPD:image
现在相当于告诉你的 \(a[i]\) 是每一位符合 1,2,3 条件的 \(b\) 的前缀和,要求一个 \(b\) ,相当于 \(a\)\(b\) 的高维前缀和(注意这里不是二进制而是三进制)
做法就是 \(b[i,4]=a[i,2]-a[i,3], b[i,5]=a[i,1]+a[i,3]-a[i,2], b[i,6]=a[i,2]-a[i,1]\)
枚举一下三进制位,每次在某一位为 0 的时候更新差分数组

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>
#define pb push_back

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 1e6+5;

int n,p3[15],a[maxn];
signed main(){
	scanf("%d",&n);
	p3[0] = 1;
	for(int i=1;i<=n;i++)p3[i] = 3*p3[i-1];
	for(int i=0;i<p3[n];i++)scanf("%d",&a[i]);
	for(int i=0;i<n;i++){
		for(int j=0;j<p3[n];j++)if((j/p3[i])%3 == 0){
			int v0=j, v1=j+p3[i], v2=j+2*p3[i];
			int w0=a[v0], w1 = a[v1], w2 = a[v2];
			a[v2]=w1-w0; a[v0]=w1-w2; a[v1]=w1-a[v2]-a[v0];
		}
	}
	for(int i=0;i<p3[n];i++)printf("%d ",a[i]);

	return 0;
}
posted @ 2023-02-06 12:48  SkyRainWind  阅读(26)  评论(0编辑  收藏  举报