2024杭电多校第七场

目录

1004-战争游戏

1007-创作乐曲

1008-循环图

1009-关于 agKc 实在不喜欢自动化于是啥都自己合成这件事

1010-故障机器人想活下去

1011-蛋糕上的草莓是蛋糕的灵魂


1004-战争游戏

首先,如果树的直径小于等于人r1*2+1 ,那么进攻方只需选择树的直 径的中点,便可一回合就把防守方炸死。

否则,若 r1*2>=r2 ,那么进攻方总可以用下面的方法把防守方逼死:

首先,进攻方选择一个与防守方的一开始所在位置距离为 r1的点作为轰炸中心,之后每回合,将轰炸中心往防 守方当前所在位置移动 1步,直到防守方移动至叶子节点,那么这一回合进攻方就能把防守方炸死。由于防守方怎么移动都不能经过轰炸中心所在的点(不然一定会被炸死),所以这种策略一定可以在一定回合内把防守方炸死。

若 r1*2<r2 ,那么无论防守方在什么地方,进攻方选择什么位置作 为轰炸中心,树上总会存在防守方可以到达的没有被轰炸的位置,此时防 守方可以获胜。

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

vector<int>to[N];
int ans = 0;
int dfs(int x,int fa) {
	int d1=0,d2=0;
	for(auto ed : to[x]) {
		int y=ed, z=1;
		if(y==fa) continue;
		int d=dfs(y,x)+z;
		if(d>=d1) d2=d1,d1=d;
		else if(d>d2) d2=d;
	}
	ans=max(ans,d1+d2);
	return d1;
}
void solve() {
	int n,s,r1,r2;
	cin>>n>>s>>r1>>r2;
	for(int i=1; i<n; i++) {
		int u,v;
		cin>>u>>v;
		to[u].push_back(v);
		to[v].push_back(u);
	}
	dfs(s,-1);
	if(ans<=2*r1||r2<=2*r1) cout<<"Kangaroo_Splay"<<'\n';
	else cout<<"General_Kangaroo"<<'\n';
	for(int i=1;i<=n;i++){
		to[i].clear();
	}
	ans=0;
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--) solve();

	return 0;
}

1007-创作乐曲

首先,题目可以转化为询问子乐曲内最多可以保留多少个音符,使这

些保留的音符构成的乐曲是“美妙动听的”。考虑这样一个性质:对于一个音高为x的音符,在最优解中,接在其

后面的音符至多有两种可能,分别为离这个音符最近的音高在 x到x+k内的音符和离这个音符最近的音高在x-k到x内的音符。

所以我们只需要从一个方向遍历的同时,维护一棵权值线段树,节点表示这一部分值所在位置的最值,如果是从后往前进行枚举的,那节点表示这一部分值所在位置的最小。

然后用一个简单的dp递推答案。

#include "bits/stdc++.h"

using namespace std;
#define int long long
const int N = 1e5 + 100;
const int M = 1e7+100;

int n, m, k;
int cnt;
int a[N];
int ls[M];
int rs[M];
int vis[M];

void update(int p, int l, int r, int w, int id) {
    vis[p] = id;
    if (l == r)return;
    int mid = (l + r) / 2;
    if (w <= mid) {
        if (!ls[p])ls[p] = ++cnt;
        update(ls[p], l, mid, w, id);
    } else {
        if (!rs[p])rs[p] = ++cnt;
        update(rs[p], mid + 1, r, w, id);
    }
}
int query(int p, int l, int r, int x, int y) {
    if (l >= x && r <= y)return vis[p];
    int mid = (l + r) >> 1;
    int ans = n + 2;
    if (x <= mid) {
        ans = min(ans, query(ls[p], l, mid, x, y));
    }
    if (y >= mid + 1) {
        ans = min(ans, query(rs[p], mid + 1, r, x, y));
    }
    return ans;
}
int nxt1[N], nxt2[N];
int dp[N];
void solve() {
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++)cin >> a[i];
    vis[0] = n + 1;
    vis[1] = n + 1;
    cnt=1;
    for (int i = n; i >= 1; i--) {
        nxt1[i] = query(1, 1, m, a[i], min(m, a[i] + k));
        nxt2[i] = query(1, 1, m, max(1ll, a[i] - k), a[i]);
        update(1, 1, m, a[i], i);
    }
    int q;
    cin >> q;
    while (q--) {
        int l,r;
        cin>>l>>r;
        for(int i=l;i<=r;i++)dp[i]=0;
        int ans=0;
        for(int i=r;i>=l;i--){
            dp[i]=1;
            if(nxt1[i]<=r)dp[i]=max(dp[i],dp[nxt1[i]]+1);
            if(nxt2[i]<=r)dp[i]=max(dp[i],dp[nxt2[i]]+1);
            ans=max(ans,dp[i]);
        }
        cout<<r-l+1-ans<<'\n';
    }
    for(int i=0;i<=cnt;i++){
        ls[i]=0;
        rs[i]=0;
        vis[i]=0;
    }
}


signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T = 1;
    cin >> T;
    while (T--)solve();
    return 0;
}

1008-循环图

方法一(赛时过了,赛后T了,杭电oj是这样的):

对于第一组样例进行解释

3 4 5 6

1 2 1

1 3 1

3 4 1

2 5 1

初始有的路径为

1->2

1->3

3->4

2->5

加一次后可增加的路劲为

4->5

4->6

6->7

5->8

因为v是小于2n的,我们以每2n个数作为一组,即1~2n,2n+1~4n……

可以发现主要有两种转移方式,

1、在这2n个之内自己转移的,比如1->2,4->5,7->8

2、在相邻的之间进行转移的,比如6->7

可以明显的发现,转移的方式是相同的,比如1->2,7->8,对2n取个模,其实都是1->2(废话,就是这么得到的),所以可以通过矩阵乘法的方式来模拟这个过程。

首先对于2n之内的数,有多少种情况可以转移到它,用一个简单的递推是好转移的,先把这个求下来。赋值成一个矩阵a

a1
a2
a3
a4
a5
a6
a7(总的)

前2n行表示能达到这个数的方案数,第2n+1行表示之前的方案数之和,如果a1~a8代表7~12那么第7位代表前6个数的方案数的总和,这个矩阵的初始值是,a1~a8代表1~6,最后一位为0

我们设一张2n*2n矩阵,设为b,来模拟2n个可能的转移,拿样例来说明(这里的1到6都是取模之后的)

1->2

1->3

3->4

2->5

4->5

4->6

6->7(相当于6->1)

5->8(相当于5->2)

1可以通过上一次的6得到所以

123456
11
2
3
4
5
6

2通过1得到,所以把第1行累加到第2行上

2通过上一次的5得到

123456
11
211
3
4
5
6

3通过1得到,所以把第1行累加到第3行上

123456
11
211
31
4
5
6

4通过3得到,所以把第3行累加到第4行上

123456
11
211
31
41
5
6

5通过2得到,所以把第2行累加到第5行上

5通过4得到,所以把第4行累加到第5行上

123456
11
211
31
41
512
6

6通过4得到,所以把第4行累加到第6行上

123456
11
211
31
41
512
61

(样例每条边的w都是1,实际操作要乘w)

把b和a两个矩阵相乘,可以发现得到的矩阵其实就是下2n个数的方案数,a1~a6就变成了a7~a12

题目要求的实际是L~R的方案数之和,只要求出前缀和相减就行,

我们改变一下b矩阵,初始值就变成l

a1a2a3a4a5a6
a11
a211
a31
a41
a512
a61
1111111

在最底下加一行,用来算a矩阵中第2n+1个数表示的前缀和。(因为矩阵乘法,b矩阵的第2n+1行*a矩阵的第1列,初值设成这样就能求前缀和)

可以快速的求出L,R,分别对应着第几对2n个数,然后用矩阵快速幂快速求出那一对对应的a矩阵,第2n+1个表示前(2n)^{k}个前缀和,用前a矩阵的2n项来补足(2n)^{k}到L 的剩余的部分,R同理,相减即为答案

#include <bits/stdc++.h>

using namespace std;
#define int long long
const int N = 210;
const int M = 1e4 + 500;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
//ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);


int n, m, l, r;

struct jz {
    int a[N][N];

    jz() {
        memset(a, 0, sizeof(a));
    }

    inline void clear() {
        memset(a, 0, sizeof(a));
    }

    inline void build_E() {
        for (int i = 1; i <= n * 2 + 1; i++) a[i][i] = 1;
    }
};

jz operator*(const jz &x, const jz &y) {
    jz res;
    for (int k = 1; k <= n * 2 + 1; k++) {
        for (int i = 1; i <= n * 2 + 1; i++) {
            for (int j = 1; j <= n * 2 + 1; j++) {
                res.a[i][j] = (res.a[i][j] + x.a[i][k] * y.a[k][j] % mod) % mod;
            }
        }
    }
    return res;
}

jz ksm(jz a, int b) {
    jz ans;
    ans.build_E();
    while (b) {
        if (b & 1)ans = ans * a;
        a = a * a;
        b >>= 1;
    }
    return ans;
}

jz mt, tmp;
jz p;

int a[N];
vector<pair<int, int>> to[N];

void solve() {
    mt.clear();
    tmp.clear();
    p.clear();

    cin >> n >> m >> l >> r;
    int k = n * 2;

    for (int i = 1; i <= k + 1; i++) {
        to[i].clear();
        a[i] = 0;
    }

    a[1] = 1;
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        to[u].push_back({v, w});
        to[u + n].push_back({v + n, w});
    }


    for (int i = 1; i <= k; i++) {
        for (auto p: to[i]) {
            int v = p.first;
            int w = p.second;
            if (v <= k)
                a[v] = (a[v] + a[i] * w % mod) % mod;
        }
        a[k + 1] = (a[k + 1] + a[i]) % mod;
    }


    for (int i = n + 1; i <= k; i++) {
        for (auto p: to[i]) {
            int v = p.first;
            int w = p.second;
            if (v > k) {
                mt.a[v % k][i] = (mt.a[v % k][i] + w) % mod;
            }
        }
    }

    for (int i = 1; i <= k; i++) {
        for (auto p: to[i]) {
            int v = p.first;
            int w = p.second;
            if (v <= k) {
                for (int j = 1; j <= k; j++) {
                    mt.a[v][j] = (mt.a[v][j] + mt.a[i][j] * w % mod) % mod;
                }
            }
        }
    }

    for (int i = 1; i <= k + 1; i++) {
        mt.a[k + 1][i] = 1;
    }
    for (int i = 1; i <= 2 * n + 1; i++) {
        p.a[i][1] = a[i];
    }
    int suml = 0, sumr = 0;
    int yl = (l - 1) % k + 1;
    int yr = (r - 1) % k + 1;
    
    l = (l - 1) / k;
    r = (r - 1) / k;
    tmp = ksm(mt, l);
    tmp = tmp * p;
    suml = tmp.a[2 * n + 1][1] % mod;



    for (int i = 1; i < yl; i++) suml = (suml + tmp.a[i][1]) % mod;

    tmp = ksm(mt, r);
    tmp = tmp * p;

    sumr = tmp.a[2 * n + 1][1]% mod;
    for (int i = 1; i <= yr; i++) sumr = (sumr + tmp.a[i][1]) % mod;

    cout << (sumr + mod - suml) % mod << '\n';
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}

方法二:待补充

1009-关于 agKc 实在不喜欢自动化于是啥都自己合成这件事

这题题意不太清楚,主要要注意几点

赛时有人询问了,不使用管理员服务的情况下,在有限的时间内可以制作出该物品,所以不存在环,因为每个物品只会作为一个配方的原料,所以形成的图形一定是森林。

考虑从最终要得到的节点开始做树形dp,

设a[u]表示u节点在它所属的配方中需要的份数,t[u]表示一些原材料需要的时间,b[u]表示制作所有所需要的u节点需要的时间,可以通过a[u]*b[v]转移得到,v是u的儿子节点。

对于最终产物的节点,如果它的儿子节点中只有一个是大于1e9,那么显然一定对这个动用管理员权力,如果有两个那么一定不成立,如果一个都没有,那么就删去权值最大的一棵子树。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
const int mod=998244353;

int n,k;
int a[N];
int t[N];
int b[N];
vector<int>to[N];


void dfs(int u){
    if(t[u]!=0){
        b[u]=a[u]*t[u];
        if(b[u]>(int)1e9)b[u]=-1;
        return;
    }
    for(auto v:to[u]){
        dfs(v);
        if(b[u]==-1)continue;
        if(b[v]==-1){
            b[u]=-1;
            continue;
        }
        b[u]+=a[u]*b[v];
        if(b[u]>(int)1e9||a[u]*b[v]>(int)1e9)b[u]=-1;
    }
    if(u==k){
        int cnt=0;
        b[u]=0;
        int mmax=0;
        for(auto v:to[u]){
            if(b[v]==-1){
                if(cnt==0)cnt++;
                else{
                    b[u]=-1;
                    break;
                }
                continue;
            }
            b[u]+=b[v];
            mmax=max(mmax,b[v]);
            if(b[u]-mmax>(int)1e9){
                b[u]=-1;
                break;
            }
        }
        if(b[u]!=-1&&cnt==0)b[u]-=mmax;
        if(b[u]>(int)1e9)b[u]=-1;
    }
}
void solve(){
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        int op;
        cin>>op;
        if(op==0){
            cin>>t[i];
        }else{
            int m;
            cin>>m;
            for(int j=1;j<=m;j++){
                int w,v;
                cin>>w>>v;
                a[v]=w;
                to[i].push_back(v);
            }
        }
    }
    a[k]=1;
    dfs(k);
    if(t[k]!=0)b[k]=t[k];
    if(b[k]==-1){
        cout<<"Impossible"<<'\n';
    }else{
        cout<<b[k]<<'\n';
    }
    for(int i=1;i<=n;i++){
        a[i]=0;
        b[i]=0;
        t[i]=0;
        to[i].clear();
    }
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int T = 1;
    cin >> T;
    while (T--) solve();

    return 0;
}

1010-故障机器人想活下去

按序遍历敌人,记录当前的总战损,用大根堆记录遭遇敌人的战损,每次需要使用烟雾弹时对堆顶的敌人使用,将其从堆中弹出并在总战损中减去,直到烟雾弹用完故障机器人死亡或故障机器人活到了第n场战斗结束时找到答案。

#include<bits/stdc++.h>

using namespace std;
#define int long long

const int N = 1e5 + 10;

int n, x, k;
int a[N];

void solve() {
    cin >> n >> x >> k;
    priority_queue<int> q;
    for (int i = 1; i <= n; i++)cin >> a[i];
    if (k > n) {
        cout << n << '\n';
        return;
    }
    int ans = 0;
    int res = 0;
    for (int i = 1; i <= n; i++) {
        res += a[i];
        q.push(a[i]);
        if (res >= x && k > 0) {
            k--;
            res-=q.top();
            q.pop();
        }
        if(res<x){
            ans++;
        }
    }
    cout<<ans<<'\n';
}


signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T = 1;
    cin >> T;
    while (T--)solve();
    return 0;
}

1011-蛋糕上的草莓是蛋糕的灵魂

要使草莓的大小尽量大,草莓切的刀数就要尽量少。

为了保证每块蛋糕上都有草莓且数量一样,必须保证最终的草莓数是蛋糕数的倍数。不切蛋糕的情况下,蛋糕数的因数较少,草莓数可以切尽量少的刀凑成蛋糕数的倍数。

设草莓的初始个数为 x,切 n 刀,则草莓数为: 2nx,蛋糕数为 y ,最终的草莓数是蛋糕数的c倍,c是正整数。 所以2nx=cy,可以求n,要保证n尽量小,n为正整数,应该用gcd来化简n的求值式子,

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
const int mod=998244353;
void solve(){
	int x,y;
	cin>>x>>y;
	int g1=__gcd(x,y);
	if((y/g1)%2&&g1!=y) x*=2;
	cout<<y<<' '<<x/g1<<'\n';
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
    cin >> t;
	while (t--) solve();

	return 0;
}

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

心刍

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

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

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

打赏作者

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

抵扣说明:

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

余额充值