2018 AICCSA Programming Contest

本文深入探讨了多项竞赛编程题目,包括树的重心、矩形分割、函数计算、序列匹配、泰迪熊日等问题,提供了详细的算法思路和代码实现,是提高编程竞赛成绩的宝贵资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A - Tree Game     Gym - 101968A 

思路:树的重心,当以树的重心为根节点时,其任一子树的节点数不大于 n / 2,

所以当只有一个重心时,谁先到达重心谁就能赢,所以我们以树的重心为根节点,求出每个节点的深度,只要先手玩家的深度 >= 后手玩家,那么先手玩家必赢,所以当只有一个重心时,所以ans = n * (n - 1) / 2, 因为当深度相同时, 玩家位置可以任意交换, 所以ans 还加上深度相同时的匹配的点数量(具体看代码) 

当有两个重心时, 第二个到达重心的玩家才能赢, 而且两个重心必定相邻, 所以我们分别以两个重心为根节点求出每个点深度(一个点只会被遍历一次,因为每个点所属的重心不同),然后减去位于不同重心深度却相同的匹配情况就好了

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 2e6 + 50;

LL n;
struct Edge
{
	int to, next;
} edge[maxn * 2];

int k, head[maxn];
void add(int a, int b){
	edge[k].to = b;
	edge[k].next = head[a];
	head[a] = k++;
}

vector<int> vec;

int getcn(int u, int pre){ // 找重心
	int flag = 1;
	int s = 1;
	for(int i = head[u]; i != -1; i = edge[i].next){
		int to = edge[i].to;
		if(to == pre){
			continue;
		}
		int t = getcn(to, u);
		if(t > (n >> 1)){
			flag = 0;
		}
		s += t;
	}
	if(n - s > (n >> 1)){
		flag = 0;
	}
	if(flag){
		vec.push_back(u);
	}
	return s;
}

int dp[maxn];
int gr[maxn]; // 记录所属的重心,只有一个时,全都属于一个重心
void dfs(int u, int pre, int g, int d){ 
	dp[u] = d;
	gr[u] = g;
	for(int i = head[u]; i != -1; i = edge[i].next){
		int to = edge[i].to;
		if(to == pre){
			continue;
		}
		dfs(to, u, g, d + 1);
	}
}

LL cnt[2][maxn];

int main() {
	int t;
	scanf("%d", &t);
	while(t--){
		k = 0;
		vec.clear();
		scanf("%I64d", &n);
		for(int i = 0; i <= n; i++){
			head[i] = -1;
			dp[i] = 0;
			cnt[0][i] = cnt[1][i] = 0;
			gr[i] = 0;
		}
		for(int i = 2; i <= n; i++){
			int to;
			scanf("%d", &to);
			add(i, to);
			add(to, i);
		}

		getcn(1, -1);
		LL ans = n * (n - 1) / 2;

		if(vec.size() == 1){
			dfs(vec[0], -1, 0, 0);
			for(int i = 1; i <= n; i++){
				cnt[0][dp[i]]++;
			}

			for(int i = 0; i <= n; i++){
				ans += cnt[0][i] * (cnt[0][i] - 1) / 2;
			}
		} else{
			dfs(vec[0], vec[1], 0, 0); // 因为不会经过父节点,所以不会访问对方的子节点
			dfs(vec[1], vec[0], 1, 0);

			for(int i = 1; i <= n; i++){
				cnt[gr[i]][dp[i]]++; // 求出每个深度的点的个数
			}

			for(int i = 0; i <= n; i++){
				ans += cnt[0][i] * (cnt[0][i] - 1) / 2;
				ans += cnt[1][i] * (cnt[1][i] - 1) / 2;
				ans -= cnt[0][i] * cnt[1][i]; // 位于不同重心,深度相同
			}
		}

		printf("%I64d\n", ans);
	}
}

B - Rectangles  Gym - 101968B 

思路:若能找到符合的方案,则一定存在一条垂直的线和水平的线是的垂直的线两边点数相等以及竖直的线两边点数相等,且不存在点在这两条线上,两条线会把区域分为四个格子,左下和右上匹配,左上和右下匹配,就是答案了,左上和右下的点数一定是相同的,左下和右上点数相同

#include <bits/stdc++.h>
using namespace std;

const int maxn = 2e6 + 60;
typedef long long LL;
struct date
{
    int x, y;
} a[maxn], b[maxn];

bool cmp1(date A, date B){
    return A.x < B.x;
}

bool cmp2(date A, date B){
    return A.y < B.y;
}
LL mod = 1e9 + 7;
LL num[maxn];
int main(){
    int t;
    scanf("%d", &t);
    num[1] = 1;
    for(LL i = 2; i < maxn; i++){
        num[i] = num[i - 1] * i % mod;
    }
    while(t--){
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= 2 * n; i++){
            scanf("%d%d", &a[i].x, &a[i].y);
            b[i].x = a[i].x;
            b[i].y = a[i].y;
        }
        sort(a + 1, a + 2 * n + 1, cmp1);
        int flag = 1;
        if(a[n].x == a[n + 1].x){
            flag = 0;
        }

        sort(b + 1, b + 2 * n + 1, cmp2);
        if(b[n].y == b[n + 1].y){
            flag = 0;
        }

        if(flag == 0){
            printf("0\n");
            continue;
        }
        double x = 1.0 * (a[n].x + a[n + 1].x) / 2;
        double y = 1.0 * (b[n].y + b[n + 1].y) / 2;
        LL cnt1 = 0;
        for(int i = 1; i <= 2 * n; i++){ // 找出坐下的点数
            if(a[i].x < x && a[i].y < y){
                cnt1++;
            }
        }

        
        LL cnt2 = n - cnt1; // n - cnt1就是左上的题目
        LL ans = 1;
        if(cnt2){
            ans = ans * num[cnt2] % mod;
        }
        if(cnt1){
            ans = ans * num[cnt1] % mod;
        }
        printf("%I64d\n", ans);
    }
}

 

C - Function  Gym - 101968C 

思路:直接打表找规律,打表找出在一定的n的情况下,每个数被加了几次,发现第 i 个数被加 C(n + 1, i) - 1 次

这是打表代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 50;
int INF = 1e9;
int n, m;

int cnt[maxn];
void dfs(int le, int ri){
    if(le == ri){
        cnt[le]++;
        return ;
    }

    for(int i = le; i <= ri; i++){
        cnt[i]++;
    }
    dfs(le, ri - 1);
    dfs(le + 1, ri);
}

int main(int argc, char const *argv[])
{
    while(1){
        memset(cnt, 0, sizeof(cnt));
        scanf("%d", &n);
        dfs(1, n);
        for(int i = 1; i <= n; i++){
            printf("%d ", cnt[i]);
        }
        printf("\n");
    }
    return 0;
}

这是AC代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 2e6 + 60;
typedef long long LL;

LL fac[maxn], inv[maxn];
LL n;
LL mod = 1e9 + 7;
LL pow(LL x, LL k){
	LL ans = 1;
	while(k){
		if(k & 1){
			ans = ans * x % mod;
		}
		x = x * x % mod;
		k >>= 1;
	}
	return ans;
}

void init(){
	fac[0] = inv[0] = 1;
	for(int i = 1; i <= n; i++){
		fac[i] = fac[i - 1] * i % mod;

	}
	inv[n] = pow(fac[n], mod - 2);
	for(int i = n - 1; i >= 1; i--){
		inv[i] = inv[i + 1] * (i + 1) % mod;
	}
}

LL Get(LL a, LL b){
	return fac[a] * inv[b] % mod * inv[a - b] % mod;
}

int ar[maxn];
int main(){
	n = 1e6 + 10;
	init();
	int t;
	scanf("%d", &t);
    while(t--){
    	scanf("%I64d", &n);
    	LL ans = 0;
    	for(int i = 1; i <= n; i++){
    		LL x;
    		scanf("%I64d", &x);
    		ans = (ans + x * (Get(n + 1, i) - 1)) % mod;
    	}
    	printf("%I64d\n", ans);
    }
    return 0;
}

D - Two Sequences  Gym - 101968D 

思路:输入的时候用两个map分别记录每个序列所拥有的某个数的个数,将两个序列具有的数全都加到 lsh 数组里,然后unique该数组,然后枚举每一个数(因为unique过了,所以相同的数只会被枚举一次),比较两个序列所拥有的该数的个数,若不同,则记录一下,最后在分类讨论输出就好了

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
int INF = 0x3f3f3f3f;

int a[maxn];
int b[maxn];
int lsh[maxn];
map<int, int> mmap1, mmap2;
int main(int argc, char const *argv[])
{
    int t;
    scanf("%d", &t);
    while(t--){
        mmap1.clear();
        mmap2.clear();
        int n, k;
        int cnt = 0;
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; i++){
            scanf("%d", &a[i]);
            mmap1[a[i]]++;
            lsh[++cnt] = a[i];
        }
        for(int i = 1; i <= n; i++){
            scanf("%d", &b[i]);
            mmap2[b[i]]++;
            lsh[++cnt] = b[i];
        }
        sort(lsh + 1, lsh + cnt + 1);
        cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;
        int res = 0;
        int numa = -1, numb = -1;
        int flag = 1;
        for(int i = 1; i <= cnt; i++){
            if(mmap1[lsh[i]] != mmap2[lsh[i]]){
                res++;
                if(res > 2){
                    flag = 0;
                    break;
                }
                if(mmap1[lsh[i]] > mmap2[lsh[i]]){
                    if(numa != -1){
                        flag = 0;
                        break;
                    }
                    numa = lsh[i];
                } else{
                     if(numb != -1){
                        flag = 0;
                        break;
                    }
                    numb = lsh[i];
                }
            }
        }
        if(flag == 0){
            printf("NO\n");
            continue;
        }
        if(res == 0){
            printf("YES\n");
        } else{
            if(abs(numa - numb) <= k){
                printf("YES\n");
            } else{
                printf("NO\n");
            }
        }
    }
    return 0;
}

E - Connecting Components  Gym - 101968E 

不会

F - Mirror  Gym - 101968F 

不会

G - TeddyBearsDay  Gym - 101968G 

思路: 分析一下容易看出,当子树(父节点是谁都无所谓)内部的供需不平衡时,才会从外部获取或者输出对应的差值,这样是可以保证费用最小(一开始还以为是最小费用流,一看数据范围。。。有点大。。。)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 60;
#define fi first
#define se second
typedef long long LL;
typedef pair<int, int>  pii;
struct Edge
{
    int to, next, w;
} edge[maxn * 2];

int k, head[maxn];

void add(int a, int b, int c){
    edge[k].to = b;
    edge[k].w = c;
    edge[k].next = head[a];
    head[a] = k++;
}

int in[maxn], out[maxn];

LL ans = 0;
pii dfs(int u, int pre, int w){
    pii tmp = pii{in[u], out[u]}; // 用来记录包含这个节点在内的子树的供需
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to, w = edge[i].w;
        if(to == pre){
            continue;
        }
        pii p = dfs(to, u, w);
        tmp.fi += p.fi, tmp.se += p.se;
    }

    ans += 1LL * abs(tmp.fi - tmp.se) * w; //该子树需要从父节点获得或者向父节点输出abs(tmp.fi - tmp.se)的泰迪熊
    return tmp;
}
int main(int argc, char const *argv[])
{
    int t;
    scanf("%d", &t);
    while(t--){
        int n;
        scanf("%d", &n);
        ans = 0;
        k = 0;
        for(int i = 1; i <= n; i++){
            head[i] = -1;
            in[i] = out[i] = 0;
        }
        for(int i = 1; i < n; i++){
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c);
            add(b, a, c);
        }
        int q;
        scanf("%d", &q);
        while(q--){
            int a, b;
            scanf("%d%d", &a, &b);
            in[b]++;
            out[a]++;
        }

        dfs(1, -1, 0);
        printf("%I64d\n", ans);
    }
    return 0;
}

 

H - Win Strategy  Gym - 101968H 

思路:emmm。。。至今不是很理解这个题意。。。感觉没有很明确的定义时间。。。队友xjb写过了。。。就是一个01背包的变形

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
int INF = 0x3f3f3f3f;


int main(int argc, char const *argv[])
{
	int t;
	scanf("%d", &t);
	while(t--){
		int dp[2000] = {0};
		int n, L;
		scanf("%d%d", &n, &L);

		for(int i = 1; i <= n; i++){
			int a, b;
			scanf("%d%d", &a, &b);

			for(int j = L; j >= a + b - 1; j--){
				dp[j] = max(dp[j], dp[j - b] + 1);
			}
		}

		printf("%d\n", dp[L]);
	}
    return 0;
}

 

I - Tours  Gym - 101968I 

思路:二分答案,然后模拟就好了,只有上一天没有用的车才能转移车站,上一天用过的车不能转移车站

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 60;
#define fi first
#define se second
typedef long long LL;
typedef pair<int, int>  pii;
int n, m;
int r;

vector<int> vec[maxn];
int main(int argc, char const *argv[])
{
    int t;
    scanf("%d", &t);
    while(t--){
        scanf("%d%d%d", &n, &m, &r);
        for(int i = 1; i <= n; i++){
            vec[i].clear();
            for(int j = 1; j <= m; j++){
                int x;
                scanf("%d", &x);
                if(x % r == 0){  //算出所需的车辆
                    x /= r;
                } else{
                    x = x / r + 1;
                }
                vec[i].push_back(x);
            }
        }
        int ans = 0;
        int le = 1, ri =  5e5;
        while(le <= ri){
            int flag = 1;
            int mid = (le + ri) >> 1;
            int x = mid;
            int sum = 0;
            for(int i = 1; i <= n; i++){
                sum += vec[i][0];
            }
            if(mid < sum){
                le = mid + 1;
                continue;
            }
            x -= sum; // 记录第一天剩下的车
            for(int j = 1; j < m; j++){
                sum = 0;
                int nx = 0;
                for(int i = 1; i <= n; i++){
                    if(vec[i][j] > vec[i][j - 1]){
                        if(x >= vec[i][j] - vec[i][j - 1]){
                            x -= (vec[i][j] - vec[i][j - 1]);
                        } else{
                            flag = 0;
                            break;
                        }
                    } else{
                        nx += (vec[i][j - 1] - vec[i][j]); // 记录当天没有用的车
                    }
                }

                x += nx; // 记录这天空闲的所有车
                if(flag == 0){
                    break;
                }
            }
            if(flag){
                ans = mid;
                ri = mid - 1;
            } else{
                le = mid + 1;
            }
        }

        printf("%d\n", ans);
    }
    return 0;
}

 

J - Restricted Vertex Cover Gym - 101968J 

思路:一开始xjb染色,怎么染都不对。。。后来看了题解。。2-sat 。。恍然大悟。。。就是个2 - sat板子题,甚至不需要记录路径x代表这个点染色, x + n代表这个点不染色,先标记所有在特殊边上的点(我们叫特殊点),然后我们对每对相邻的(有边连接)特殊点处理,具体看代码, 这里2 - sat 的模板就是白书上的

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
int n, m;
struct Edge
{
    int to, next;
} edge[maxn * 2], edge2[maxn * 2];

int k, head[maxn];
int k2, head2[maxn];

void add(int a, int b){
    edge[k].to = b;
    edge[k].next = head[a];
    head[a] = k++;
    edge2[k2].to = a;
    edge2[k2].next = head2[b];
    head2[b] = k2++;
}
int vis[maxn];
struct date
{
    int a, b, c;
} arr[maxn];

vector<int> vec;
void dfs(int u){
    vis[u] = 1;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        if(vis[to]){
            continue;
        }
        dfs(to);
    }
    vec.push_back(u);
}

int cmp[maxn];

void dfs2(int u, int x){
    vis[u] = 1;
    cmp[u] = x;
    for(int i = head2[u]; i != -1; i = edge2[i].next){
        int to = edge2[i].to;
        if(!vis[to]){
            dfs2(to, x);
        }
    }
}

int scc(){
    memset(vis, 0, sizeof(vis));
    vec.clear();
    for(int i = 1; i <= 2 * n; i++){
        if(!vis[i]){
            dfs(i);
        }
    }

    memset(vis, 0, sizeof(vis));
    int num = 0;
    int len = vec.size();
    for(int i = len - 1; i >= 0; i--){
        if(!vis[vec[i]]){
            dfs2(vec[i], ++num);
        }
    }
    return num;
}

int main(int argc, char const *argv[])
{
    int t;
    scanf("%d", &t);
    while(t--){
        scanf("%d%d", &n, &m);
        k = 0, k2 = 0;
        memset(head, -1, sizeof(head));
        memset(head2, -1, sizeof(head2));
        for(int i = 0; i < m; i++){
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            if(c){
                vis[a] = vis[b] = 1;
            }
            arr[i].a = a;
            arr[i].b = b;
            arr[i].c = c;
        }

        for(int i = 0; i < m; i++){
            int a, b, c;
            a = arr[i].a;
            b = arr[i].b;
            c = arr[i].c;
            if(vis[a] && vis[b]){
                if(c){ // 特殊边
                    add(a, b + n);
                    add(b + n, a);
                    add(b, a + n);
                    add(a + n, b);
                } else{ //非特殊边
                    add(a + n, b);
                    add(b + n, a);
                }
            }
        }
        scc();
        int flag = 1;
        for(int i = 1; i <= n; i++){
            if(cmp[i] == cmp[i + n]){
                flag = 0;
                break;
            }
        }
        if(flag){
            printf("YES\n");
        } else{
            printf("NO\n");
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值