2024杭电多校第八场

目录

1004-cats 的重力拼图

1005-cats 的二分答案

1006-cats 的最小生成树

1007-cats 的 k-xor

1008-cats 的数据结构

1012-cats 的电脑中毒


1004-cats 的重力拼图

主要考虑以下几种情况

1、在四个边界点上,那么只能跑过矩形最外面的一个框。

2、在最外面一个框上,但是不在四个点上,仅可以向对面跑一条长为n或者m的边,然后加上最外面的一个框

3、在除最外面一个框的任意一个点,那么就要比较它向左右和上下跑哪个更优,选哪一个。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
const int mod=998244353;
void solve(){
	int n,m,a,b,ans,maxm=0;
	cin>>n>>m>>a>>b;
	if(n==1){
		cout<<m<<'\n';
	}else if(m==1){
		cout<<n<<'\n';
	}else{
		ans=(n+m)*2-4;
		if((a==1||a==n)&&(b==1||b==m)){
			maxm=0;
		}else if(a==1||a==n){
			maxm=n-2;
		}else if(b==1||b==m){
			maxm=m-2;
		}else{
			maxm=max(n-2,m-2);
		}
		cout<<ans+maxm<<'\n';
	}
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
    cin >> t;
	while (t--) solve();

	return 0;
}

1005-cats 的二分答案

方法一:

设dp[i][j]表示,还可以使用j次越界,现在需要二分的距离是i可以的个数。

那么转移就是每一个二分,如果向左的话就消耗了一次越界,j-1

向有不消耗越界,长度根据l,r 和mid的距离计算,用记忆化的方法

#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);


map<int, int> mp[60];

int dfs(int len, int x) {
    if (mp[x].count(len)) {
        return mp[x][len];
    }
    if (len <= 1)return len;
    int res = 1;
    int mid = (1 + len) / 2;
    if (x)res += dfs(mid - 1, x - 1);
    res += dfs(len - mid, x);
    return mp[x][len] = res;
}

void solve() {
    int l, r, k;
    cin >> l >> r >> k;

    if (k >= 60) {
        cout << r - l + 1 << '\n';
        return;
    }
    cout << dfs(r - l + 1, k) <<'\n';
}


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

方法二:

通过手玩多组数据,可以发现,题目要求的,其实是1到r-l+1中,二进制1的个数小于等于k的数字的个数。

用组合数学的方法,用最高位的1出发,在每一个二进制为1的点计算贡献并累加

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
const int mod=998244353;
int C[100][100],m2[100];
int g[100];
void init(){
	m2[0]=1;
	for(int i=1;i<65;i++) m2[i]=m2[i-1]*2;
	C[0][0]=1,C[1][0]=1,C[1][1]=1;
	for(int i=2;i<65;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++){
			C[i][j]=C[i-1][j-1]+C[i-1][j];
		}
	}
}
void solve(){
	memset(g,0,sizeof(g));
	int l,r,k,jsq=0,mc=0,ans=0;
	cin>>l>>r>>k;
	int le=r-l+1;
	while(m2[mc+1]<=le) mc++;
	int sx=mc;
	while(le){
		if(le>=m2[mc]){
			le-=m2[mc];
			for(int i=0;i<=mc;i++){
				if(i==0) g[i-1+jsq+1]+=C[mc][i];
				else g[i-1+jsq]+=C[mc][i];
			}
			jsq++;
		}
		mc--;
	}
	for(int i=0;i<=min(k,sx);i++)
		ans+=g[i];
	cout<<ans<<'\n';
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	init();
	int t = 1;
    cin >> t;
	while (t--) solve();

	return 0;
}

1006-cats 的最小生成树

考虑在顺序枚举的过程中就计算出答案,那么就要开多个并查集来维护对于每一棵最小生成树的边的联通关系。

一共可以构造的最小生成树的数量最多是m/(n-1)棵,每个需要n个节点,所以需要的节点的个数是一个可以存的范围,考虑存成一个数组。1~n表示第一棵树的n个点,n+1~2n表示第二棵树,以此类推。

对于每次读进来的两个点,二分它们第一次可以连边的最小生成树的编号,对它们进行连边。并统计每颗树连了几条边,最后对完整的最小生成树进行编号的输出。

#include <bits/stdc++.h>

using namespace std;
#define int long long
const int N = 3e6 + 10;



int fa[N], a[N];
int n, m;
int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);

}
int cnt[N];
void solve() {
    cin>>n>>m;
    for (int i = 0; i <= m / (n - 1) + 1; i++) {
        int n0 = i * (n);
        for (int j = 1; j <= n; j++) {
            fa[n0 + j] = n0 + j;

        }
    }
    for (int i = 0; i <= 2 * m; i++)cnt[i] = 0;
    for (int i = 0; i <= n; i++)a[i] = 0;
    for (int i = 1; i <= m; i++) {
        int u,v;
        cin>>u>>v;
        int l = 0, r = (m / (n - 1));
        while (l <= r) {
            int mid = (l + r) / 2;
            int fx = find(mid * n + u);
            int fy = find(mid * n + v);
            if (fx == fy) l = mid + 1;
            else r = mid - 1;
        }
        int fx = find(l * n + u);
        int fy = find(l * n + v);
        fa[fy] = fx;
        a[i] = (l + 1);
        cnt[l + 1]++;
    }
    if (m < n - 1) {

        for (int i = 1; i <= m; i++) {
            cout<<-1<<" ";
        }
        cout<<'\n';
        return;
    }

    for (int i = 1; i <= m; i++) {
        if (cnt[a[i]] != n - 1 || a[i] > (m / (n - 1))) {
            cout<<-1<<" ";
        }
        else {
            cout<<a[i]<<" ";
        }
    }
    cout<<'\n';
}

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

1007-cats 的 k-xor

对于小范围的数据,可以进行暴力。

对于大的数据,这么考虑:

首先, k-xor 运算,相当于在正常的a+b之下,舍弃了一部分值(放弃了进位的操作),变成了c,这也是为什么如果a-b<c一定是0个,a-b=c是无穷(极大值都可以)

如果进制数大于50000(一个大于\sqrt{2e9}的值),那么在该进制下a和b一定最多只有两位,且进位只能发生在第一位,因此舍弃的值假设为k,那么只有a+b-k可能等于c,所以大于500000的进制中,只有k也就是(a+b-c)有可能是一个答案。

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


int check(int x,int y,int k){
    int ans=0;
    vector<int>v;
    while(x||y){
        int v1=x%k;
        x/=k;
        int v2=y%k;
        y/=k;
        v.push_back((v1+v2)%k);
    }
    for(int i=v.size()-1;i>=0;i--){
        ans=ans*k+v[i];
    }
    return ans;
}

void solve(){
    int ans=0;
    int a,b,c;
    cin>>a>>b>>c;
    if(a+b-c==0){
        cout<<-1<<'\n';
        return;
    }else if(a+b-c<0){
        cout<<0<<'\n';
        return;
    }else if(a+b-c>50000){
        if(check(a,b,a+b-c)==c)ans++;
    }
    for(int i=2;i<=50000;i++){
        if(check(a,b,i)==c){
            ans++;
        }
    }
    cout<<ans<<'\n';
}

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

    return 0;
}

1008-cats 的数据结构

首先要满足字典序最小,我们先让a尽量优秀,当然得满足诸如a1要尽量小,a2要尽量小。

由于题目低于祖先关系设计的要求的存在,比如1是3的父亲,3是2的父亲。要满足a2尽量小,我们可以填1,但如果1是2的父亲,2是3的父亲,a2最少只能填2,来满足a2大于a3

对于两颗子树,显然我们要先去优先填子树里最小的那个节点更小的一颗,所以考虑进行一次树形dp求出对于每个节点,它们的子树中最小的节点的编号是多少,然后对于这个对每个父亲结点的所有子树进行排序。

因为有题目要求的二维的一个关系的存在,我们可以用树dfs给树打左右区间编号的方法,在dfs开始和结束的位置分别给a和b数组进行赋值。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+50;
const int mod=998244353;
int a[N],b[N],js,js2,n;
vector<pair<int,int>>v[N];
void dfs(int x,int fa,int z){
    for(int i=0;i<v[x].size();i++){
        dfs(v[x][i].second,x,i);
        if(x>1) v[fa][z].first=min(v[fa][z].first,v[x][i].first);
    }
    sort(v[x].begin(),v[x].end());
}
void dfs1(int x){
    b[x]=js2--;
    for(auto p:v[x]){
        dfs1(p.second);
    }
    a[x]=js++;
}
void solve(){
    int u;
    cin>>n;
    for(int i=2;i<=n;i++){
        cin>>u;
        v[u].push_back({i,i});
    }
    dfs(1,0,0);
    js=1;
    js2=n;
    dfs1(1);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<' '<<b[i]<<' ';
    cout<<'\n';
    for(int i=1;i<=n;i++)
        v[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;
}

1012-cats 的电脑中毒

先考虑两个字符串去达到所有的n位二进制的情况,可以把这想象成一个简单的追及问题,先抛开相同的位置不谈,对于两个字符串不相等的位置,假设k位不相同,那么两个字符串至少只要(k+1)除以2步就可以达到这些位置的全部状态。

这里要注意一个问题,有奇数个位不相同时,最后一步的时候,其实只要一方进行一步操作就行,这里另一方会多出来一步,下面会提到。

对于两者相同的部分,走n-k步一定可以完成,对于上述的奇数的情况,多出来的一步可以在此处进行减一的优化。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int long long
const int lim = 2e6 + 10;
const int N = 2e6 + 10;
const int mod = 1e9 + 7;

void solve() {
    int n;
    cin >> n;
    string s1, s2, s3;
    cin >> s1 >> s2 >> s3;
    int b2 = 0, b3 = 0, b1 = 0;
    for (int i = 0; i < n; i++) {
        if (s1[i] == s2[i])b2++;
        if (s1[i] == s3[i])b3++;
        if (s2[i] == s3[i])b1++;
    }
    int step = min(b2, b3);
    step = min(step, b1);
    int ans = 0;
    if ((n - step) % 2 == 0) {
        ans = (n - step) / 2 + step;
    } else {
        ans = (n - step + 1) / 2 + step - 1;
    }
    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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

心刍

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

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

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

打赏作者

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

抵扣说明:

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

余额充值