河南省第十一届ACM大学生程序设计竞赛 部分题解

题目链接

A 计划日

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;

bool leapyear(int y)
{
	if (y % 400 == 0 || y % 4 == 0 && y % 100 != 0)
		return true;
	return false;
}
int main()
{
#ifdef LOCAL
	//freopen("C:/input.txt", "r", stdin);
#endif
	int month[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
	int T;
	cin >> T;
	while (T--)
	{
		int y, m, d, w, n;
		scanf("%4d%2d%2d %d %d", &y, &m, &d, &w, &n);
		//cout << y << " " << m << " " << d << endl;
 		--w;
		while (n--)
		{
			d++;
			if (d > month[m] + (m == 2 && leapyear(y)))
				d = 1, m++;
			if (m > 12)
				y++, m = 1;
			w = (w + 1) % 7;
		}
		printf("%04d%02d%02d %d\n", y, m, d, w + 1);
	}

	return 0;	
}

B 治安管理 <前缀和>

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 10;
int sum[N], a[N], b[N];

int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	int T;
	cin >> T;
	while (T--)
	{
		memset(sum, 0, sizeof(sum));
		int n, m, s, f;
		cin >> n >> m >> s >> f;
		for (int i = 0; i < n; ++i)
			scanf("%d", &a[i]);
		for (int i = 0; i < n; ++i)
			scanf("%d", &b[i]);
		for (int i = 0; i < n; ++i)
			sum[a[i]]++, sum[b[i]]--;
		int mx = 0, mi = INF;
		for (int i = 0; i < N; ++i)
		{
			sum[i] += sum[i - 1];
			if (i >= s && i < f)
				mx = max(mx, sum[i]), mi = min(mi, sum[i]);
		}
		if (mi >= m)
			printf("YES %d\n", mx);
		else
			printf("NO %d\n", mi);
	}

	return 0;	
}

C 山区修路 <dp>

状态转移时使用u、d两个数组求出上个状态高度从第到高、从高到低的最小值前缀。然后进行转移减少复杂度。

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 10;
int f[N][110]; //第i个位置为j高度的代价
int u[110], d[110];
int a[N];

int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	int T;
	cin >> T;
	while (T--)
	{
		memset(f, 0x3f, sizeof(f));
		memset(u, 0, sizeof(u));
		memset(d, 0, sizeof(d));
		int n, x;
		cin >> n >> x;
		for (int i = 1; i <= n; ++i)
			scanf("%d", &a[i]);
		u[0] = d[101] = INF;
		for (int i = 1; i <= n; ++i) //位置 
		{
			for (int j = a[i]; j <= 100; ++j) //当前高度
			{
				f[i][j] = (a[i] - j) * (a[i] - j);
				f[i][j] += min(u[j], d[j]); //前面状态转移到j高度的代价 
			}
			memset(u, 0x3f, sizeof(u)); //最值前缀
			memset(d, 0x3f, sizeof(d));
			for (int j = 1; j <= 100; ++j)
				u[j] = min(u[j - 1] + x, f[i][j]);
			for (int j = 100; j >= 1; --j)
				d[j] = min(d[j + 1] + x, f[i][j]);
		}
		int ans = INF;
		for (int i = 1; i <= 100; ++i)
			ans = min(ans, f[n][i]);
		cout << ans << endl;
	}

	return 0;
}

D 求XF+闭包 <模拟>

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
string a[50], b[50], ans;
int n, m, k;

bool solve()
{
	for (int i = 0; i < k; ++i)
	{
		//cout << a[i] << " " << b[i] << endl;
		int flag = 1;
		for (int j = 0; j < a[i].size(); ++j)
			if (ans.find(a[i][j]) == -1)
				flag = 0;
		if (flag)
		{
			int sz = ans.size();
			ans += b[i];
			sort(ans.begin(), ans.end());
			ans.erase(unique(ans.begin(), ans.end()), ans.end());
			if (sz < ans.size())
				return 1;
		}
	}
	return 0;
}
int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	int T;
	cin >> T;
	while (T--)
	{
		cin >> n >> m >> k;
		cin >> ans >> ans;
		for (int i = 0; i < k; ++i)
			cin >> a[i] >> b[i];
		while (solve());
		cout << ans << endl;
	}

	return 0;
}

E 物流配送 <费用流>

把每个点的需求量和原有量进行比较,原有较多的作为源点,需求较多的作为汇点。建立超级源点和所有源点相连,容量为原有量-需求量,代价为0,建立超级汇点类似操作。
题目所给边建立双向边,代价为题目所给容量无穷大。最后跑最小费用最大流,最小代价即为答案。

#include <stdio.h>
#include <bits/stdc++.h>
#define fst first
#define sed second
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e3 + 10;
int a[N], b[N];
bool vis[N];
int dis[N], pre[N];

struct edge
{
	int v, w, c, nxt; //容量 代价
}e[N * 8];
int h[N], idx;

void AddEdge(int u, int v, int w, int c)
{
	e[idx] = { v, w, c, h[u] };
	h[u] = idx++;
}
bool SPFA(int st, int ed)
{
	queue<int> q;
	memset(dis, 0x3f, sizeof(dis));
	memset(pre, -1, sizeof(pre));
	dis[st] = 0;
	vis[st] = 1;
	q.push(st);
	while (!q.empty())
	{
		int u = q.front(); q.pop(), vis[u] = 0;
		for (int i = h[u]; ~i; i = e[i].nxt)
		{
			int v = e[i].v, w = e[i].w, c = e[i].c; //w流量 c代价
			if (dis[v] > dis[u] + c && w)
			{
				dis[v] = dis[u] + c;
				pre[v] = i;
				if (!vis[v])
					q.push(v), vis[v] = 1;
			}
		}
	}
	return pre[ed] != -1;
}
void MCMF(int st, int ed, ll &cost)
{
	cost = 0;
	while (SPFA(st, ed))
	{
		int mi = INF;
		for (int i = pre[ed]; ~i; i = pre[e[i ^ 1].v])
			mi = min(mi, e[i].w);
		for (int i = pre[ed]; ~i; i = pre[e[i ^ 1].v])
		{
			e[i].w -= mi, e[i ^ 1].w += mi;
			cost += 1LL * mi * e[i].c;
		}
	}
}
int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	memset(h, -1, sizeof(h));
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i)
		scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d", &b[i]);
		if (a[i] > b[i]) //源点
		{
			AddEdge(0, i, a[i] - b[i], 0); //无代价权值差容量
			AddEdge(i, 0, 0, 0);
		}
		else if (a[i] < b[i]) //汇点
		{
			AddEdge(i, n + 1, b[i] - a[i], 0);
			AddEdge(n + 1, i, 0, 0);
		}
	}
	for (int i = 1; i < n; ++i)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		AddEdge(u, v, INF, w);
		AddEdge(v, u, 0, -w);
		AddEdge(v, u, INF, w);
		AddEdge(u, v, 0, -w);
	}
	ll cost = 0;
	MCMF(0, n + 1, cost);
	cout << cost << endl;

	return 0;
}

G Checkpoints <tarjan>

如果能从起点到终点再回到起点则起点和终点在同一个强连通分量内。找出起点和终点的强连通分量。尝试暴力删除一些点。
如果删除再次Tarjan得到强连通分量,如果起点和终点依然在同一个强连通内则说明方案可行,并且标记当前节点下次不再处理。
因为此次处理会把所有包含删除当前点的状态都遍历到。

#include <stdio.h>
#include <bits/stdc++.h>
#define fst first
#define sed second
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 110;
int n, m, st, ed;
vector<int> e[N];
int dfn[N], low[N], num, idx; //节点编号 子树能到的最小编号 强连通分量数
bool vis[N], slv[N]; //栈内节点 已处理节点
int stk[N], tot;
int ans;

void Tarjan(int x, int *blo) //结果保存在blo内
{
	dfn[x] = low[x] = ++idx;
	stk[++tot] = x, vis[x] = 1;
	for (int y : e[x])
		if (!dfn[y])
			Tarjan(y, blo), low[x] = min(low[x], low[y]);
		else if (vis[y])
			low[x] = min(low[x], low[y]);
	if (low[x] == dfn[x])
	{
		int y;
		++num;
		do 
		{
			y = stk[tot--], vis[y] = 0;
			blo[y] = num;
		} while (y != x);
	}
}
void solve(int del) //删除了del节点后
{
	int blo[N]; //强连通最小编号 每层独立
	num = idx = 0;
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	Tarjan(st, blo); //从起点开始得到强连通分量
	if (blo[st] != blo[ed]) //起点终点不是强连通则无法过去再返回
		return ;
	slv[del] = 1; //删除当前节点后依然满足则标记以后不再处理 因为接下来的递归会把包含删除当前节点的所有状态全部处理到
	int cnt = 0; //和起点在同一强连通内的点数
	for (int i = 1; i <= n; ++i)
		if (blo[i] == blo[st])
			++cnt;
	ans = min(ans, cnt);
	for (int i = 1; i <= n; ++i) //尝试删除每个点并递归处理
		if (i != st && i != ed && !slv[i]) //不删除起点终点
		{
			vector<int> cp = e[i]; //拷贝当前节点的边
			e[i].clear(); //并删除
			solve(i); //递归处理
			e[i] = cp; //还原
		}
}
int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	int T;
	cin >> T;
	while (T--)
	{
		cin >> n >> m >> st >> ed;
		for (int i = 1; i <= n; ++i)
			e[i].clear();
		for (int i = 0; i < m; ++i)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			e[u].push_back(v);
		}
		ans = INF;
		memset(slv, 0, sizeof(slv));
		solve(0);
		cout << ans - 2 << endl; //减去起点终点
	}

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值