2019级吉林大学XCPC集训队选拔赛 - 第二轮 补题记录

B题

在这里插入图片描述
在这里插入图片描述
前缀和+计算贡献

对于a[1]:
a[1]仅在l=1时出现,
r=1,a[1]*b[1]; r=2,a[1]*(b[1]+b[2]),…,r=n,a[1]*(b[1]+b[2]+…+b[n]),因此b[1]贡献了n次,b[2]贡献了n-1次,…,b[n]贡献了1次

对于a[2]:
a[2]仅在l=1和l=2时出现
l=1时,对于a[2]有b[2]贡献n-1次,…,b[n]贡献1次
l=2时,对于a[2]有b[2]贡献n-1次,…,b[n]贡献1次

以此类推得出:
a[i]对答案的贡献为i*a[i]*(b[i]*(n-i+1)+b[i+1]*(n-i)+…+b[n]*1)

代码如下:

#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
const ll mod = 998244353;
ll n, a[maxn], b[maxn];
int main(void) {
	scanf("%lld", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
		a[i] = (a[i] * i) % mod;
	}
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &b[i]);
		b[i] = (b[i] * (n - i + 1)) % mod;
		b[i] = (b[i] + b[i - 1]) % mod;
	}
	ll ans = 0;
	for (int i = 1; i <= n; i++) 
		ans = (ans + a[i] * (b[n] - b[i - 1]) % mod) % mod;
	printf("%lld\n", ans);
	return 0;
}

C题

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
对于最后一个式子,要用到矩阵快速幂(求斐波那契数)+逆元(除法)

根据欧拉定理,2^x同余2^(x%p)(mod p)

参考:https://zhuanlan.zhihu.com/p/35060143

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll p = 998244353;
const ll p_1 = p - 1;
ll x1, x2, tmp[101];
struct Maxtrix {
	ll m[2][2];
	Maxtrix() {
		memset(m, 0, sizeof(m));
	}
};
ll gcd(ll a, ll b) {
	while (b) {
		ll t = b;
		b = a % b;
		a = t;
	}
	return a;
}
ll ksm(ll a, ll n) {
	ll ret = 1;
	while (n) {
		if (n & 1)ret = (ret * a) % p;
		a = (a * a) % p;
		n >>= 1;
	}
	return ret % p;
}
Maxtrix Multi(Maxtrix a, Maxtrix b) {
	Maxtrix res;
	res.m[0][0] = (a.m[0][0] * b.m[0][0] + a.m[0][1] * b.m[1][0]) % p_1;
	res.m[0][1] = (a.m[0][0] * b.m[0][1] + a.m[0][1] * b.m[1][1]) % p_1;
	res.m[1][0] = (a.m[1][0] * b.m[0][0] + a.m[1][1] * b.m[1][0]) % p_1;
	res.m[1][1] = (a.m[1][0] * b.m[0][1] + a.m[1][1] * b.m[1][1]) % p_1;
	return res;
}
void fastm(ll &x,ll n) {
	Maxtrix res, a;
	res.m[0][0] = res.m[1][1] = 1;
	a.m[0][0] = a.m[0][1] = a.m[1][0] = 1;
	while (n) {
		if (n & 1)
			res = Multi(res, a);
		a = Multi(a, a);
		n >>= 1;
	}
	x = res.m[0][0] % (p - 1);
}
int main(void) {
	scanf("%lld%lld", &x1, &x2);
	if (x1 > x2)swap(x1, x2);
	ll pow = gcd(x1, x2);
	ll fx1, fx2, fx3, f0 = 0, f1 = 1;
	fastm(fx1, x1 - 1); fastm(fx2, x2 - 1); fastm(fx3, pow - 1);
	ll	a = ((ksm(2, fx1) + p) - 1) % p;
	ll	b = ((ksm(2, fx2) + p) - 1) % p;
	ll	c = ((ksm(2, fx3) + p) - 1) % p;
	c = ksm(c, p - 2);
	printf("%lld\n", (a * b) % p * c % p);
	return 0;
}

D题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码如下:

#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
int n, q, a[maxn];
ll c[maxn], t[maxn];
struct Shu {
	int L, R;
	ll Sum;
	Shu() :L(0), R(0), Sum(0) {}
}tree[maxn << 2];
void update(int x) {
	tree[x].Sum = tree[x << 1].Sum + tree[(x << 1) | 1].Sum;
}
void Build_Tree(int node, int L, int R) {
	tree[node].L = L;
	tree[node].R = R;
	if (L == R) {
		tree[node].Sum = t[L];
		return;
	}
	int mid = (L + R) / 2;
	Build_Tree(node * 2, L, mid);
	Build_Tree(node * 2 + 1, mid + 1, R);
	update(node);
}
ll Search(int node, int L, int R) {
	if (tree[node].L >= L && tree[node].R <= R)//全部在区间之内
		return tree[node].Sum;
	if (tree[node].R < L || tree[node].L > R)//完全在区间之外
		return 0;
	ll ret = 0;
	if (tree[node << 1].R >= L)ret += Search(node << 1, L, R);//左子树的右边在区间之内
	if (tree[(node << 1) | 1].L <= R)ret += Search((node << 1) | 1, L, R);//右子树的左边在区间之内
	return ret;
}
void Change(int node, int pos, ll data) {
	if (tree[node].L == tree[node].R) {
		tree[node].Sum = data;
		return;
	}
	if (pos <= tree[node << 1].R)Change(node << 1, pos, data);//在左子树中
	else Change((node << 1) | 1, pos, data);//在右子树中
	update(node);
}
int main(void) {
	scanf("%d %d", &n, &q);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &c[i]);
		t[i] = 1LL * a[i] * c[i];
	}
	Build_Tree(1, 1, n);
	while (q--) {
		int cmd, L, R;
		scanf("%d %d %d", &cmd, &L, &R);
		if (cmd == 1)
			printf("%lld\n", 4LL * Search(1, L, R));
		else {
			c[L] += R;
			Change(1, L, t[L] = 1LL * a[L] * c[L]);
		}
	}
	return 0;
}

E题

在这里插入图片描述
在这里插入图片描述
题解:以零点作为超级起点,连接每个初始点燃的结点,计算出零点到每个点的距离,即每个结点的点燃时间,然后遍历每个结点,去找它们的相邻结点,根据距离差和边权来计算可能需要的最大时间。

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 1e5 + 5;
int n, m, burned[maxn], vis[maxn];
ll dis[maxn];
struct edge {
	int u, v, w;
};
vector<edge>e[maxn];
struct node {
	int id;
	ll dis;
	bool operator <(node a) const{
		return dis > a.dis;
	}
};
bool cmp(int a, int b) {
	return dis[a] < dis[b];
}
int main(void) {
	memset(dis, inf, sizeof(dis));
	scanf("%d %d", &n, &m);
	for (int i = 1; i < n; i++) {
		int u, v, w;
		scanf("%d %d %d", &u, &v, &w);
		e[u].push_back(edge{ u,v,w });
		e[v].push_back(edge{ v,u,w });
	}
	dis[0] = 0;
	priority_queue<node>pq;
	for (int i = 0; i < m; i++) {
		int x; scanf("%d", &x);
		dis[x] = 0;
		pq.push(node{ x,0 });
	}
	while (!pq.empty()) {
		node head = pq.top(); pq.pop();
		int u = head.id;
		if (burned[u])continue;
		burned[u] = 1;
		for (int i = 0; i < e[u].size(); i++) {
			int v = e[u][i].v;
			if (dis[v] > e[u][i].w + dis[u]) {
				dis[v] = e[u][i].w + dis[u];
				pq.push(node{ v,dis[v] });
			}
		}
	}
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < e[i].size(); j++) {
			int v = e[i][j].v;
			if (vis[v])continue;
			ans = max(ans, max(2 * max(dis[i], dis[v]), e[i][j].w + dis[i] + dis[v]));
		}
		vis[i] = 1;
	}
	printf("%lld\n", ans);
	return 0;
}

F题

在这里插入图片描述
在这里插入图片描述
Dinic算法+拆点

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 5005;//拆点:200 无向边变有向边 1000*2 2000+200反向弧,总共4400
const int inf = 0x3f3f3f3f;
int n, m, num;
int depth[maxn], cur[maxn], start = 1, tend;
int head[maxn], cnt;
struct edge{
    int to, cap, next;
} e[maxn];
void addedge(int u, int v, int w){
    e[cnt].to = v;
    e[cnt].cap = w;
    e[cnt].next = head[u];
    head[u] = cnt++;//记录u指向的最后一条边
}
bool bfs(){
    memset(depth, -1, sizeof(depth));
    int Q[maxn];
    int Thead = 0, Ttail = 0;
    Q[Ttail++] = start;;
    depth[start] = 0;
    while (Thead < Ttail){
        int u = Q[Thead];
        if (u == tend)
            return true;
        for (int i = head[u]; i != -1; i = e[i].next){
            int v = e[i].to;
            if (depth[v] == -1 && e[i].cap > 0){
                depth[v] = depth[u] + 1;
                Q[Ttail++] = v;
            }
        }
        Thead++;
    }
    return false;//汇点是否成功标号,也就是说是否找到增广路
}
int dfs(int u, int cap){
    if (u == tend) return cap;
    int flow = 0, f;
    for (int &i = cur[u]; i != -1; i = e[i].next){
        int v = e[i].to;
        if (depth[v] == depth[u] + 1 && e[i].cap){
            f = dfs(v, min(cap - flow, e[i].cap));
            e[i].cap -= f;
            e[i ^ 1].cap += f;
            flow += f;
            if (flow == cap)
                break;
        }
    }
    if (!flow)
        depth[u] = -2;//防止重搜
    return flow;
}
int maxflow(){
    int flow = 0, f, limit = n << 1;
    while (bfs()){
        for (int i = 1; i <= limit; i++)
            cur[i] = head[i];
        while (f = dfs(start, inf))
            flow += f;
    }
    return flow;
}
int main(void){
    memset(head, -1, sizeof(head));
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        int w; scanf("%d", &w);
        addedge(i, i + n, w);//将i拆成 i为入点,i+n为出点
        addedge(i + n, i, 0);//反向弧
    }
    for (int i = 1; i <= m; i++) {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        addedge(u + n, v, w);//u的出点和v的入点连接
        addedge(v, u + n, 0);//<u,v>反向弧
        addedge(u, v + n, 0);//<v,u>反向弧
        addedge(v + n, u, w);//v的出点和u的入点连接
    }
    scanf("%d%d", &tend, &num);
    tend += n;//要经过tend->tend+n,所以得把tend+n看作汇点
    int ans = maxflow();
    printf("%.8f\n", 1.0 * num / ans);
    return 0;
}

G题

在这里插入图片描述
在这里插入图片描述

#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn = 16;
int n, x[maxn], y[maxn], dp[1 << maxn], IsOneEdge[1 << maxn], limit;
vector<int>edge;
int dfs(int subset) {
	for (int i = 0; i < edge.size(); i++) {
		if (!((edge[i] & subset) ^ edge[i])) {
			int left = subset ^ edge[i];
			if (dp[left] == -1)dp[left] = dfs(left);
			if (!dp[left])return 1;
		}
	}
	return 0;
}
int main(void) {
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
		scanf("%d %d", &x[i], &y[i]);
	limit = 1 << n;
	dp[0] = 0;
	for (int i = 1; i < limit; i++) {
		dp[i] = -1;
		int hav[maxn], len = 0;
		bool flag = true;
		for (int j = 0; j < n; j++)
			if (i & (1 << j))
				hav[len++] = j;
		if (len >= 2) {
			int dy = y[hav[0]] - y[hav[1]], dx = x[hav[0]] - x[hav[1]];
			for (int k = 2; k < len; k++) {
				int dy2 = y[hav[0]] - y[hav[k]], dx2 = x[hav[0]] - x[hav[k]];
				if (dy * dx2 != dy2 * dx) {
					flag = false;
					break;
				}
			}
		}
		if (flag) {
			IsOneEdge[i] = 1;
			edge.push_back(i);
			dp[i] = 1;
		}
	}
	printf("%s\n", dfs((1 << n) - 1) == 1 ? "zyh" : "fzj");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

JILIN.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值