【Codeforces】1120 Round #543 Div. 1 B-F简要题解

传送门:CF1120


B.Once in a casino

一道贪心坑了好多人啊(包括我)。

首先存在以下情况是无解的:

不考虑 0 − 1 , 9 + 1 0-1,9+1 01,9+1的合法性,贪心依次让 1 − ( n − 1 ) 1-(n-1) 1(n1),即 ( 1 , b 1 − a 1 ) , ( 2 , b 2 − ( a 2 + ( b 1 − a 1 ) ) ) (1,b_1-a_1),(2,b_2-(a_2+(b_1-a_1))) (1,b1a1),(2,b2(a2+(b1a1)))( ( i , j ) (i,j) (i,j)表示将 a i , a i + 1 a_i,a_{i+1} ai,ai+1同时 + j +j +j)。
a n a_n an是不能单独操作的,若 a n ≠ b n a_n\neq b_n an̸=bn,则必然无解。

可以证明除了上面的情况以外一定有解(证明略):
每次贪心操作最前可以操作的位置,步数一定是最少的。


C.Compress String

SAM DP裸题,不解释。


D.Power Tree

转化一下题目的要求(设叶子结点个数为 k k k):,选择 k k k个结点,使得每个结点子树内不经过其它选择的结点所能到达的叶结点都是唯一且各不相同的,要求最小化 ∑ C i \sum C_i Ci

考虑 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示以 i i i为根的子树还剩 0 / 1 0/1 0/1个叶子结点没被覆盖的最优答案。

答案即 f [ 1 ] [ 0 ] f[1][0] f[1][0]

如何寻找哪些点可能出现在最优点内呢?似乎可以再DP一遍。

考虑从状态 g [ 1 ] [ 0 ] g[1][0] g[1][0]出发进行 b f s bfs bfs(设 s u m u = ∑ v ∈ s o n u f [ v ] [ 0 ] sum_u=\sum \limits_{v\in son_u}f[v][0] sumu=vsonuf[v][0]):

  • 状态 g [ u ] [ k ] g[u][k] g[u][k]会被访问到当且仅当全局最优解可以由 f [ u ] [ k ] f[u][k] f[u][k]转移而来
  • k = 0 k=0 k=0
    枚举 v ∈ s o n [ u ] v\in son[u] vson[u],若存在 f [ u ] [ 0 ] = s u m u − f [ v ] [ 0 ] + f [ v ] [ 1 ] + C u f[u][0]=sum_u-f[v][0]+f[v][1]+C_u f[u][0]=sumuf[v][0]+f[v][1]+Cu,则 u u u可能出现在最优解内,且 f [ v ] [ 1 ] , f [ t ] [ 0 ] ( t ∈ s o n [ u ] , t ≠ v ) f[v][1],f[t][0](t\in son[u],t\neq v) f[v][1],f[t][0](tson[u],t̸=v)均合法,将未访问过的状态压入队列。
  • k = 1 k=1 k=1
    枚举 v ∈ s o n [ u ] v\in son[u] vson[u],若存在 f [ u ] [ 1 ] = s u m u − f [ v ] [ 0 ] + f [ v ] [ 1 ] f[u][1]=sum_u-f[v][0]+f[v][1] f[u][1]=sumuf[v][0]+f[v][1],则 f [ v ] [ 1 ] , f [ t ] [ 0 ] ( t ∈ s o n [ u ] , t ≠ v ) f[v][1],f[t][0](t\in son[u],t\neq v) f[v][1],f[t][0](tson[u],t̸=v)均合法,将未访问过的状态压入队列。

故可以在线性时间内求出可能出现在在最优集合中的点。

这题的套路十分值得推广。

#include
#define rep(i,j,k) for(i=j;i<=k;++i)
#define pb push_back
#define mkp make_pair
#define fi first
#define sc second
#define pii pair
using namespace std;
const int N=2e5+10;
typedef long long ll;

int n,c[N],ans[N],f[N],num;
ll dp[N][2],sum[N];
bool vs[N][2],cn[N];
vector<int>g[N];

void dfs(int x)
{
	if(f[x] && g[x].size()==1){
		dp[x][0]=c[x];dp[x][1]=0;
		return;
	}
	for(int y:g[x]) if(y^f[x]){f[y]=x;dfs(y);sum[x]+=dp[y][0];}
	for(int y:g[x]) if(y^f[x]){
		dp[x][0]=min(dp[x][0],sum[x]-dp[y][0]+dp[y][1]+c[x]);
		dp[x][1]=min(dp[x][1],sum[x]-dp[y][0]+dp[y][1]);
	}
	dp[x][0]=min(dp[x][0],sum[x]);
}

queue<pii>que;
pii tp;

int main(){
	memset(dp,0x7f,sizeof(dp));
    int i,j,x,y;
	scanf("%d",&n);
	rep(i,1,n) scanf("%d",&c[i]);
	rep(i,2,n){
		scanf("%d%d",&x,&y);
		g[x].pb(y);g[y].pb(x);
	}
	dfs(1);que.push(mkp(1,0));vs[1][0]=true;
	for(;!que.empty();){
		tp=que.front();que.pop();x=tp.fi;j=tp.sc;i=-1;
		if(f[x] && g[x].size()==1){
			if(!j) cn[x]=true;continue;
		}
		if((!j)&& dp[x][0]==sum[x]){
			for(int y:g[x]) if((y^f[x])&&(!vs[y][0])){
				vs[y][0]=true,que.push(mkp(y,0));
			}
		}
		for(int y:g[x]) if(y^f[x])
			if(((!j) && dp[x][0]==sum[x]-dp[y][0]+dp[y][1]+c[x])
			||(j && dp[x][1]==sum[x]-dp[y][0]+dp[y][1])){
				if(!j) cn[x]=true;
				if(i==-1){
				   i=y;
				   for(int v:g[x]) if((v^f[x])&&(v^y)&&(!vs[v][0])){
				   	  vs[v][0]=true;que.push(mkp(v,0));
				   }
				}else if(i<N){
				  if(!vs[i][0]) vs[i][0]=true,que.push(mkp(i,0));i=N+1;
				}
				if(!vs[y][1]) vs[y][1]=true,que.push(mkp(y,1));
			}
	}
	rep(i,1,n) if(cn[i]) ans[++num]=i;
	printf("%I64d %d\n",dp[1][0],num);
	rep(i,1,num) printf("%d ",ans[i]);
	return 0;
}

E.The very same Munchhausen

膜的这份代码

考虑是否能拆位处理:

由低位到高位枚举,设 f [ x ] [ i ] [ j ] [ 0 / 1 ] f[x][i][j][0/1] f[x][i][j][0/1]表示枚举到第 x x x位,前 x − 1 x-1 x1位构成的数 × a \times a ×a后累积到当前位的需要加上的值为 i i i,前 x − 1 x-1 x1位构成的数 × a \times a ×a的数位之和 × a \times a ×a与前 x − 1 x-1 x1个数位之和的差为 j j j,是否所有数位均为0时的状态是否合法。
f [ x ] [ i ] [ j ] [ 0 / 1 ] = 1 f[x][i][j][0/1]=1 f[x][i][j][0/1]=1表示当前状态合法, f [ x ] [ i ] [ j ] [ 0 / 1 ] = 0 f[x][i][j][0/1]=0 f[x][i][j][0/1]=0表示不合法。

观察转移可以发现:

  • x x x这一维可以省略
  • 求解 f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]可以转成 0 − 9 0-9 09枚举当前位向外 b f s bfs bfs
  • 答案状态即 f [ 0 ] [ 0 ] [ 1 ] f[0][0][1] f[0][0][1],为输出答案,记录 d i g [ i ] [ j ] [ 0 / 1 ] dig[i][j][0/1] dig[i][j][0/1]表示最高位的值( 0 − 9 0-9 09), p r e [ i ] [ j ] [ 0 / 1 ] pre[i][j][0/1] pre[i][j][0/1]三元组表示状态 f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]的前驱。
  • 计算得到 i ≤ 1000 i\leq 1000 i1000,而 − 2000 ≤ j ≤ 2000 -2000\leq j\leq 2000 2000j2000(仅仅膜代码得到,并不会证明),从而保证了复杂度。

Orz

#include 

using namespace std;

typedef long long ll;

const int N = 4008;

struct triple
{
  int first, second, third;
};

bool vis[1000][N][2];
triple par[1000][N][2];
int dg[1000][N][2];

string solve(int a)
{
  int kek = 2002;
  queue <triple> q;
  for (int dig = 0; dig < 10; dig++)
  {
    int ndig = (dig * a) % 10;
    int nj = (dig * a) / 10;
    bool flag = (dig != 0);
    vis[nj][kek + ndig * a - dig][flag] = true;
    par[nj][kek + ndig * a - dig][flag] = {-1, -1};
    dg[nj][kek + ndig * a - dig][flag] = dig;
    q.push({nj, kek + ndig * a - dig, flag});
  }
  while (!q.empty())
  {
    int i = q.front().first, j = q.front().second, flag = q.front().third;
    if (i == 0 && j == kek && flag)
    {
      int x = i, y = j, z = flag;
      string str = "";
      while (x != -1)
      {
        if (!str.empty() || dg[x][y][z] != 0)
          str += (dg[x][y][z] + '0');
        auto go = par[x][y][z];
        x = go.first, y = go.second,z = go.third;
      }
      return str;
      //break;
    }
    q.pop();
    for (int dig = 0; dig < 10; dig++)
    {
      int ndig = (i + a * dig) % 10;
      int ni = (i + a * dig) / 10;
      int nval = j + ndig * a - dig;
      int nflag = (flag || (dig != 0));
      if (nval >= 0 && nval < N && !vis[ni][nval][nflag])
      {
        vis[ni][nval][nflag] = true;
        par[ni][nval][nflag] = {i, j, flag};
        dg[ni][nval][nflag] = dig;
        q.push({ni, nval, nflag});
      }
    }
  }
  return "-1";
}

int main()
{
#ifndef ONLINE_JUDGE
 // freopen("a.in", "r", stdin);
#endif
  ios::sync_with_stdio(0);
  cin.tie(0);
  int a;
  cin >> a;
  cout << solve(a) << endl;
}

F.Secret Letters

很好的思维题,大力分析一波:

一旦 i i i时刻小W/小P把信放在了R那里,另一方取走信同时再放一封,这一方由取走信再放一封…也就是说到了 R R R之后,每轮 W , P W,P W,P交替时都需要去 R R R处。
(因为一封信在R处放到结束的花费是 C ( t n + 1 − t i ) C(t_{n+1}-t_i) C(tn+1ti),如果每次交替放,总花费也是 C ( t n + 1 − t i ) C(t_{n+1}-t_i) C(tn+1ti),R处更优,所以每轮都会去取,而且安排第一个去取最优)

考虑枚举 i i i表示从 i i i开始有信放在R那里,所有情况取 min ⁡ \min min即可。

STO V–o_o–V

#include 
using namespace std;
using ull = uint64_t;
using ll = int64_t;
using ld = long double;

const int N = 100228;
char tp[N];
ll a[N];

ll f[N];

const ll INF = (1ll << 60);

int main() {
#ifdef BZ
    freopen("input.txt", "r", stdin);
#endif  
    ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); cout.setf(ios::fixed); cout.precision(20);
    int n;
    ll c, d;
    cin >> n >> c >> d;
    for (int i = 0; i < n; ++i) {
        cin >> a[i] >> tp[i];
    }

    cin >> a[n];
    tp[n] = 'X';

    ll ans = d * n;

    ll s = 0;

    ll last = a[n];

    for (int i = n - 1; i >= 0; --i) {
        if (tp[i] == tp[i + 1]) {
            s += min(d, (last - a[i + 1]) * c);
        } else {
            last = a[i + 1];
        }

        ans = min(ans, c * (a[n] - a[i]) + s + d * i);
    }

    cout << ans << "\n";
} 

你可能感兴趣的:(树形DP,贪心,构造,找规律,妙,CF)