Educational Codeforces Round 125 (Rated for Div. 2)

某蒟蒻又来写题解了,今天补题效率挺高, 可能是因为这场比较简单吧,今天依然选择倒序讲题

E. Star MST

题目大意 : 给定一个 n n n 个结点的无向完全图, 边权为 1 1 1 ~ k k k, 给出以下定义: 如果与 1 1 1 相连的边都是最小生成树中的边, 那么这样的图,就称为完全美丽图, 换句话来说, 最小生成树的边 都要与 1 1 1 相连

看到数据范围, 能够感觉出要用 D P DP DP
我们从小到大依次枚举图中的最小边权 k k k, 因此每条边的边权可以取的范围就是 k k k ~ w w w , 我们可以预处理出 w − k + 1 w - k + 1 wk+1 的 次幂,

我们可以给出以下的状态定义
状态表示 f [ k ] [ i ] f[k][i] f[k][i]: 选出 i i i 条边, 并且最大边权为 k k k 的方案数
状态计算: f [ w ] [ i + j ] = ( f [ w ] [ i + j ] + f [ w − 1 ] [ i ] ∗ C [ n − i − 1 ] [ j ] ∗ p o w w − k + 1 [ i ∗ j + j ∗ ( j − 1 ) / 2 ] ) f[w][i + j] = (f[w][i + j] + f[w - 1][i] * C[n - i - 1][j] * pow_{w- k + 1}[i* j + j * (j - 1) / 2]) % mod f[w][i+j]=(f[w][i+j]+f[w1][i]C[ni1][j]powwk+1[ij+j(j1)/2])


首先解释一下这个状态转移方程, 在最小生成树中, 我们先选出 i i i 条边,最大的边权为 w w w 再选出 j j j 条边,这些边的边权都为w + 1, C [ n − i + 1 ] [ j ] C[n - i + 1][j] C[ni+1][j] 的意思就是 已经选出了 i i i 条边, 总共有 n − 1 n - 1 n1 条边, 还剩下 n − i + 1 n - i + 1 ni+1 条边没选, 在剩下的边中选 j j j 条边, p o w w − k + 1 [ i ∗ j + j ∗ ( j − 1 ) / 2 ] pow_{w- k + 1}[i* j + j * (j - 1) / 2] powwk+1[ij+j(j1)/2] 的意思是, 因为在最小生成树中最大边权为 k k k,所以其他边的边权必须要大于等于 k + 1 k + 1 k+1 ,所以其他边可以取的值为 k k k ~ w w w, 一共 w − k + 1 w - k + 1 wk+1 种, i ∗ j i * j ij 就是 i , j i,j i,j 之间边的数量, j * (j - 1) / 2 就是 j j j 条边之间边的数量


因为每层状态只用到了上一层的状态, 所以可以进行降维操作

f [ i + j ] = ( f [ i + j ] + f [ i ] ∗ C [ n − i − 1 ] [ j ] ∗ p o w w − k + 1 [ i ∗ j + j ∗ ( j − 1 ) / 2 ] ) f[i + j] = (f[i + j] + f[i] * C[n - i - 1][j] * pow_{w- k + 1}[i* j + j * (j - 1) / 2]) % mod f[i+j]=(f[i+j]+f[i]C[ni1][j]powwk+1[ij+j(j1)/2])

#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define x first
#define y second
typedef pair<int,int>PII;
#define debug printf("debug\n");

const int N = 5e5 + 10, INF = 0x3f3f3f3f, mod = 998244353;
int t;
int f[N], C[300][300],pow1[N], dp[N];

void solve()
{
	int n, k;
	cin >> n >> k;

	f[0] = 1;
	for(int i = 0; i <= n; i ++ )  //预处理组合数
	{
		C[i][i] = C[i][0] = 1;
		for(int j = 1; j < i; j ++ )
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
	}
	for(int w = 1; w <= k; w ++ )  //枚举前j条边中的最大边权
	{
		for(int i = 0; i < n; i ++ ) dp[i] = f[i], f[i] = 0; //备份上一层的状态
		//这里为什么要用备份? 因为我们更新的时候要用到上一层的状态, 更新的状态用的是这一层的状态
		pow1[0] = 1; //初始化
		for(int i = 1; i <= n * n; i ++ ) pow1[i] = pow1[i - 1] * (k - w + 1) % mod;  //预处理出k - w + 1的次幂

		for(int i = 0; i < n; i ++ )  //枚举i条边  这里的边是最小生成树中的边
			for(int j = 0; j < n; j ++)  //枚举j条边 
				if(i + j <= n - 1) //必须合法
				f[i + j] = (f[i + j] + dp[i] * C[n - i - 1][j] * pow1[i * j + j * (j - 1) / 2] ) % mod; 
	}
	cout << f[n - 1] << '\n';
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	//cin >> t;
		solve();
}


D. For Gamers. By Gamers.

题意分析: 你有 C C C 个金币, 有 n n n 种 士兵, 有 m m m 个怪物, 对于每一个怪物, 你只能雇佣一种士兵, 不限个数, 但是每种士兵的雇佣的总价格价格不能超过 C C C, 每种士兵的价格,伤害, 血量为 c , d , h , c,d,h, c,d,h, 怪物的伤害,血量为 d , h d,h d,h
对于一个怪物, 如果不能打赢他则输出 − 1 -1 1, 能则输出需要雇佣士兵的最小金币数, 士兵与怪物的战斗是持续的,并且同时攻击,而不是每秒攻击一次

不难看出, 每种士兵不限个数, 有点像完全背包, 当然, 我们可以这么去处理
首先, 因为战斗是持续的, 所以 h a / d b > h b / d a h_a / d_ b > h_b / d_a ha/db>hb/da, 可以转化成 h a ∗ d a > h b ∗ d b h_a * d_a > h_b * d_b hada>hbdb
状态表示: f [ i ] f[i] f[i]: 用恰好 i i i 个金币, 造成的伤害的集合(这里的伤害指的是 h h h d d d 的乘积)
状态属性: 伤害的最大值
状态计算 : f [ j ] = m a x ( f [ j ] , f [ i ] ∗ j / i ) ; f[j] = max(f[j], f[i] * j / i); f[j]=max(f[j],f[i]j/i);

对于这个状态转移方程, j j j 是代表雇佣某一个兵种的总花费, i i i 是该兵种的单价

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N = 5e6 + 10;

int f[N];
void solve()
{
    int n, C, m;
    cin >> n >> C;
    
    for(int i = 1; i <= n; i ++ )  //初始化将伤害值存入数组
    {
        int h, c, d;
        cin >> c >> d >> h;
        f[c] = max(f[c], d * h);
    }
    for(int i = 1; i <= C; i ++ )  //雇佣士兵的单价
    {
        f[i] = max(f[i], f[i - 1]);  //不雇佣这种士兵, 直接继承上一层的状态
        for(int j = 2 * i; j <= C; j += i )   //雇佣这种士兵的总价格
        f[j] = max(f[j], f[i] * j / i);
    }
    
   // cout << f[C] << '\n';
    cin >> m;
    
    while(m -- )
    {
        int h, d;
        cin >> d >> h;
        if(d * h >= f[C])   //在不超过C个金币的前提下,不存在能打过该怪物的情况
        cout << -1<< '\n';
        //二分查找 找到第一个大于d * h 的最小值
        else cout << upper_bound(f + 1, f + 1 + C, d * h) - f << ' ';
    }
    
    
}
signed main()
{
    solve();
}

C. For Gamers. By Gamers.

题意分析: 就是给定一个括号序列, 对于括号序列中, 如果存在最短的前缀满足以下条件之一:

  1. 是常规的括号序列(即’(‘与’)’ 一 一 配对)
  2. 是长度至少为 2 2 2 的回文串

可以进行一次操作直接将其删掉, 注意是删去最短的!
不难发现,对于一个括号序列, 只存在 4 4 4 种情况, ( ) () (), ) ( )( )(, ( ( (( ((, ) ) )) )), 这四种中 只有 ) ( )( )( 不能直接删去, 在这种情况下, 再遇到 ) ) ) 就可以删去了
所以删去前缀的条件有两种:

  1. 出现 ′ ( ′ '(' (
  2. 遇到相同的 ′ ) ′ ')' )
#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define x first
#define y second
typedef pair<int,int>PII;
#define debug printf("debug\n");

const int N = 5e5 + 10, INF = 0x3f3f3f3f;
int T;

 
void solve()
{
	int n, res = 0;
	cin >> n;
	string arr;
    cin >> arr;
    vector<char>s;
    for(int i = 0; i < (int)arr.size(); i ++ )
    {
        s.push_back(arr[i]);  //插入
        if(s.size() == 1) continue;  //只剩一个时, 不能删
        //1. 遇到( 直接删, 2. 遇到相同, 直接删
        if( *s.begin() == '(' || (*s.begin() == s.back())) 
        {
            s.clear();
            res ++;
        }
    }
    cout << res << " " << s.size() << '\n';
	 
}
signed main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

	cin >> T;
	while(T -- )
		solve();
}

B. XY Sequence

题意分析: 给定一个长度为 n + 1 n + 1 n+1 的序列, a 0 = 0 a_0 = 0 a0=0, 请你给 a 1 a_1 a1 ~ a n a_n an 赋值, 使得他们每一项都小于等于 B B B, 并且总和最大
赋值规则如下: a i = a i − 1 + X a_i = a_ {i - 1} + X ai=ai1+X或者 a i = a i − 1 − Y a_i = a_{i - 1} - Y ai=ai1Y

不难发现我们可以先一直加 X X X, 当发现加 X X X 超过了 B B B 时, 就减去 Y Y Y, 然后又加X, 总之就是 能加就加, 不能加就减

#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define x first
#define y second
typedef pair<int,int>PII;
#define debug printf("debug\n");

const int N = 1e5 + 10, INF = 0x3f3f3f3f;
int t;

void solve()
{
	int n, B, x, y;
	cin >> n >> B >> x >> y;  //读入

		int sum = 0, num = 0;
		for(int i = 1; i <= n; i ++ )  
		{
			if(num + x > B)  //不能加就减
				num -= y;
			else    //能加就加
				num += x;
			sum += num;
		}
		cout << sum << '\n';
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

	cin >> t;
	while(t -- )
		solve();
}

A. Integer Moves

题意分析: 从 ( 0 , 0 ) (0,0) (0,0) 点出发, 每次任意移动, 但是移动必须满足欧几里得距离必须为完全平方数, 请问走到点 ( x , y ) (x,y) (x,y) 至少需要多少步

分析: 首先, 终点就是 ( 0 , 0 ) (0,0) (0,0) 直接返回 0 0 0, 然后如果终点到起点的欧几里得距离就是完全平方数,则可以直接跳到终点, 返回 1 1 1, 否则返回 2 2 2, 因为我们可以先跳到 ( x , 0 ) (x,0) (x,0), 再跳到 ( x , y ) (x,y) (x,y)

#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define x first
#define y second
typedef pair<int,int>PII;
#define debug printf("debug\n");

const int N = 1e5 + 10, INF = 0x3f3f3f3f;
int t;

void solve()
{
	int x, y;
		cin >> x >> y;
			if(x == 0 && y == 0 ) cout << 0 << '\n';
			else 
			{
				bool flag = 0;   //标记
				int sum = x * x + y * y;  //欧几里得距离
				for(int i = 1; i <= sum; i ++ )
					if(i * i == sum)
					{
						flag = 1;
						break;
					}
				if(flag) cout << 1 << '\n';  //直接是完全平方数
				else cout << 2 << '\n';
			}

}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

	cin >> t;
	while(t -- )
		solve();
}

完结啦!!! 给个赞吧, 不容易哦

  • 12
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

广西小蒟蒻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值