首页 > 技术文章 > 第十八届同济大学程序设计竞赛暨高校网络友谊赛

DreamW1ngs 2021-05-24 11:45 原文

比赛地址

A(dfs)

题目链接

解析:
直接使用dfs,模拟他的他的判断过程即可。注意用一些trick,可以将节点进行重编号(使得叶子节点按照满二叉树先序遍历的顺序进行编号),如果某个节点\(a\)是另一个节点\(b\)的祖宗节点,则\(a\)一定是\(b\)的前缀,这样就可以去判断是否有冲突

注意:

  1. 尽管探索右儿子时可能已经分配完所有信道,但还是要添加这次查询次数
  2. 同时这道题可以进行优化,对于每个祖宗节点\(cur\),二叉树还剩下\(x\)层他后代的叶子节点范围是可以计算的(\([cur\times2^x,(cur+1)\times2^x-1]\)),这样就可以对\(dat\)数组进行二分查询,复杂度从\(O(n^2)\rightarrow O(nlog(n))\)
#include<bits/stdc++.h>
using namespace std;

int n, k;

const int maxn = 5000;
int dat[maxn];

int dfs(int cur, int x) {
	int cnt = 0;
	for (int i = 0; i < k; ++i)
		if (dat[i] >> x == cur) ++cnt; //trick
	if (cnt <= 1) return 1;
	return dfs(cur << 1, x - 1) + dfs(cur << 1 | 1, x - 1) + 1;
}

int main() {
	scanf("%d%d", &n, &k);
	for (int i = 0; i < k; ++i)
		scanf("%d", &dat[i]), dat[i] += n - 1;
	printf("%d", dfs(1, log(n) / log(2)));
}

C(dp or 组合数学)

题目链接
⭐⭐

解析:

  • 第一种做法(dp) 复杂度\(O(n)\)
    定义\(dp[i]\)\(i\)被拆分的方案数,那么对于任意一个数\(i\),考虑他的最后一项可以为任意数\(x(k\le x\le i\),那么就不难得到状态转移方程\(dp[i]=\sum_{x=k}^idp[i-x]\),注意到任意一个数都可以由它自己直接构成,所以\(dp[0]=0\)。且状态转移方程由于与区间连续和有关,所以可对dp数组的前缀和进行维护,进行\(O(1)\)查询

注意:
进一步优化发现可以将前缀和数组与\(dp\)数组合并

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

const int maxn = 55;
int dat[maxn];

int main() {
	int T, n;
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &n);
		bool ok = true;
		for (int i = 0; i < n; ++i) {
			scanf("%d", &dat[i]);
			if (i && dat[i] < dat[i - 1]) ok = false;
		}
		if (ok) printf("0\n");
		else {
			if (dat[0] == n && dat[n - 1] == 1) printf("3\n");
			else if (dat[0] == 1 || dat[n - 1] == n) printf("1\n");
			else printf("2\n");
		}
	}
}
  • 第二种做法(组合数学) 预处理复杂度\(O(n)\),算法复杂度\((O(\lfloor\frac{n}{k}\rfloor))\)
    考虑利用组合数学的思想解题,先有引理,对于一个\(n\)个木棍,分成\(x\)堆(每堆至少有1个),总共有\(C_{n-1}^{x-1}\)种方案(插空法),那么对于这道题就相当于变形题,既可以先在每堆种放置\(k-1\)个,再按引理的方式继续拜访,那么最后答案就是对各种分堆个数方案数的和
#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e6 + 5;
const long long mod = 1e9 + 7;
long long inv[maxn], jc[maxn];

long long q_pow(long long a, long long b) {
	long long ans = 1;
	while (b) {
		if (b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}

typedef long long ll;

ll C(int n, int m) {
	return jc[n] * inv[m] % mod * inv[n - m] % mod;
}

int main() {
	ll n, k;
	scanf("%lld%lld", &n, &k);
	inv[0] = inv[1] = jc[0] = jc[1] = 1;
	for (int i = 2; i <= n; ++i)
		jc[i] = jc[i - 1] * i % mod;
	inv[n] = q_pow(jc[n], mod - 2);
	for (int i = n - 1; i; --i)
		inv[i] = inv[i + 1] * (i + 1) % mod;
	int end = n / k;
	ll ans = 0;
	for (int i = 1; i <= end; ++i) {
		ans = (ans + C(n - i * (k - 1) - 1, i - 1)) % mod;
	}
	printf("%lld", ans);
}

D(思维)

题目链接
⭐⭐

解析:
好像就是之前cf的一道原题,由于时刻要保证区间内\(0,1\)的数量相等,所以相隔\(k\)个距离的字符也必须相等,因此在保证字符串可以满足上述条件的基础上,再去观察前\(k\)个字符是否可以满足\(0,1\)的数量相等

#include<bits/stdc++.h>

using namespace std;

int n, k;

const int maxn = 1000000 + 5;
char str[maxn];

void no() {
	printf("No");
	exit(0);
}

void yes() {
	printf("Yes");
	exit(0);
}

int main() {
	scanf("%d%d", &n, &k);
	scanf("%s", str);
	if (k & 1) no();
	int t[2] = { 0 };
	int s = '0' + '1';
	for (int i = 0; i < k; ++i) {
		if (str[i] == '0' || str[i] == '1') {
			++t[str[i] == '1'];
			for (int j = i; j < n; j += k)
				if (str[j] == s - str[i]) no();
		}
		else {
			char c = 0;
			for (int j = i; j < n; j += k)
				if (!c && str[j] != '?')
					c = str[j];
				else if (c && str[j] != c) no();
			if (c)
				++t[c == '1'];
		}
	}
	if (t[1] > k / 2 || t[0] > k / 2) no();
	yes();
}

F(线性DP)

题目链接
⭐⭐

解析:
在如果没有要求第一个和最后一个珠子互相邻近的情况下,可以很容易定义\(dp[i][j]\)代表到第\(i\)行,选择\(j\)颜色的珠子时的最大价值
所以问题转化成了如何解决相邻的问题。可以通过指定开始珠子的颜色为红色,或者为蓝色进行\(dp\),这样就完美解决了问题

\[dp[i][0]=\max(dp[i-1][0],dp[i-1][1])+v\\ dp[i][1]=dp[i-1][0]+v\\ ~\\ ans=\begin{cases} \max(dp[n-1][0],dp[n-1][1]) &\text{if initial color is blue}\\ dp[n-1][0] & \text{if initial color is red} \end{cases} \]

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

typedef long long ll;
const int maxn = 1e6 + 10;
const ll INF = -2e17;
int T, w, n, m;
ll dp[maxn][2], a[maxn], v[maxn][2], ans;
int f(int x, int y) {
    return x * m + y;
}
int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        for (int i = 0; i < n; ++i) {
            v[i][0] = v[i][1] = INF;
            for (int j = 0; j < m; ++j) {
                scanf("%lld", &a[f(i, j)]);
            }
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                scanf("%d", &w);
                v[i][w] = max(v[i][w], a[f(i, j)]);
            }
        }
        if (n == 1) {
            printf("%lld\n", max(v[0][0], v[0][1]));
            continue;
        }
        dp[0][0] = v[0][0];
        dp[0][1] = INF;
        for (int j = 1; j < n; ++j) {
            dp[j][0] = max(dp[j - 1][0], dp[j - 1][1]) + v[j][0];
            dp[j][1] = dp[j - 1][0] + v[j][1];
        }
        ans = max(dp[n - 1][0], dp[n - 1][1]);
        dp[0][1] = v[0][1];
        dp[0][0] = INF;
        for (int j = 1; j < n; ++j) {
            dp[j][0] = max(dp[j - 1][0], dp[j - 1][1]) + v[j][0];
            dp[j][1] = dp[j - 1][0] + v[j][1];
        }
        ans = max(ans, dp[n - 1][0]);
        printf("%lld\n", ans < 0 ? -1 : ans);
    }
    return 0;
}

G(最小费用最大流)

题目链接
⭐⭐

解析:
非常典型的最小费用最大流的模板题,构建超级源点连接有空余车的城市,构建超级汇点连接缺车的城市,直接跑就行了

#include<bits/stdc++.h>

using namespace std;

const int INF = 0x3f3f3f3f;

namespace MinCostMaxFlow
{
	const static int MAXN = 1000;//node
	const static int MAXE = 10000;//edge
	struct Edge
	{
		int from, to, next, cap, flow;
		long long cost;
		Edge() {}
		Edge(int u, int v, int c, int f, long long _c, int nxt) :from(u), to(v), cap(c), flow(f), cost(_c), next(nxt) {}
	}edge[MAXE];
	int head[MAXN], tol, N, start, end;
	int pre[MAXN];
	long long dis[MAXN];
	bool vis[MAXN];
	//Function
	void init()
	{
		N = MAXN;
		tol = 0;
		memset(head, -1, sizeof(head));
	}
	void link(int u, int v, int cap, int cost)//s->t,cap,cost
	{
		edge[tol] = Edge(u, v, cap, 0, cost, head[u]); head[u] = tol++;
		edge[tol] = Edge(v, u, 0, 0, -cost, head[v]); head[v] = tol++;
	}
	bool spfa()
	{
		queue<int>Q;
		for (int i = start; i <= end; i++) dis[i] = 0x3f3f3f3f3f3f3f3f;
		for (int i = start; i <= end; i++) vis[i] = false;
		for (int i = start; i <= end; i++) pre[i] = -1;
		dis[start] = 0;
		vis[start] = true;
		Q.push(start);
		while (!Q.empty())
		{
			int u = Q.front();
			Q.pop();
			vis[u] = false;
			for (int i = head[u]; i != -1; i = edge[i].next)
			{
				int v = edge[i].to;
				if (edge[i].cap > edge[i].flow && dis[v] > dis[u] + edge[i].cost)
				{
					dis[v] = dis[u] + edge[i].cost;
					pre[v] = i;
					if (!vis[v])
					{
						vis[v] = true;
						Q.push(v);
					}
				}
			}
		}
		if (pre[end] == -1) return false;
		else return true;
	}
	int MCMF(long long& cost)
	{
		cost = 0;
		int maxflow = 0;
		while (spfa())
		{
			int Min = INF;
			for (int i = pre[end]; i != -1; i = pre[edge[i ^ 1].to])
				Min = min(Min, edge[i].cap - edge[i].flow);
			//MIN=min(Min,goal-maxflow);
			for (int i = pre[end]; i != -1; i = pre[edge[i ^ 1].to])
			{
				edge[i].flow += Min;
				edge[i ^ 1].flow -= Min;
				cost += edge[i].cost * Min;
			}
			maxflow += Min;
			// if(maxflow==goal) break;
		}
		return maxflow;
	}
};

const int maxn = 500 + 5;
int dat[maxn];

int main() {
	int T, n, m, a, b, c;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &n, &m);
		MinCostMaxFlow::init();
		MinCostMaxFlow::start = 0;
		MinCostMaxFlow::end = n + 1;
		int tot = 0;
		for (int i = 1; i <= n; ++i)
			scanf("%d", &dat[i]), tot += dat[i];
		while (m--) {
			scanf("%d%d%d", &a, &b, &c);
			MinCostMaxFlow::link(a, b, INF, c);
			MinCostMaxFlow::link(b, a, INF, c);
		}
		if (tot % n) {
			printf("-1\n");
			continue;
		}
		tot /= n;
		int top = 0;
		for (int i = 1; i <= n; ++i) {
			if (dat[i] > tot) MinCostMaxFlow::link(MinCostMaxFlow::start, i, dat[i] - tot, 0), top += dat[i] - tot;
			else if (dat[i] < tot) MinCostMaxFlow::link(i, MinCostMaxFlow::end, tot - dat[i], 0);
		}
		long long cost = 0;
		if (MinCostMaxFlow::MCMF(cost) != top)
			printf("-1\n");
		else
			printf("%lld\n", cost);
	}
}

J(SG函数+找规律)

题目链接
⭐⭐⭐⭐

解析:
可以构造一个新的子游戏,子游戏可以在树上任意一点下棋,这样根据SG定理,答案就成了连接在根节点(1号节点)上平行子游戏的异或和
对于子游戏的SG值,简单画了几个树形进行\(mex\)操作可以发现,任一游戏的SG值等于以他为根节点的子游戏SG值得异或加1(具体证明待补...)
猜测过程

#include<bits/stdc++.h>

using namespace std;

int n;
const int maxn = 1e5 + 5;
vector<int> e[maxn];

int get_sg(int cur, int fa) {
	int t = 0;
	for (auto& i : e[cur])
		if (fa != i)
			t ^= get_sg(i, cur);
	return t + 1;
}

int main() {
	int T, a, b;
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &n);
		for (int i = 1; i <= n; ++i) e[i].clear();
		for (int i = 1; i < n; ++i) {
			scanf("%d%d", &a, &b);
			e[a].push_back(b), e[b].push_back(a);
		}
		int ans = 0;
		for (auto& i : e[1])
			ans ^= get_sg(i, 1);
		printf("%s\n", ans ? "NO" : "YES");
	}
}

推荐阅读