首页 > 技术文章 > 2018冬令营模拟测试赛(十七)

xiao-ju-ruo-xjr 2018-01-18 22:50 原文

2018冬令营模拟测试赛(十七)

[Problem A]Tree

试题描述

QAQ

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

这个数据范围肯定是树上背包了。

\(f(i, j, k)\) 表示子树 \(i\) 中选择了 \(j\) 个节点,路径与根的连接情况为 \(k\),具体地:

  • \(k = 0\) 时,路径的两个端点都在子树内部;
  • \(k = 1\) 时,路径的一个端点是 \(i\),另一个端点在子树内部;
  • \(k = 2\) 时,路径的两个端点都是 \(i\)

这个树上背包的过程是:初始时只有选择根节点的情况(此时路径两端点当然都是 \(i\)),然后考虑加入一个子树,那么现在就是要讨论新路径的两个端点是否在根,若不在根,则在新加入的子树中或者原来的子树中;具体地:

  • 两个节点都不在根,那么有三种情况:
    • 两个端点都在原来的子树中;
    • 两个端点都在新加入的子树中;
    • 一个端点在新加入的子树中,另一个在原来子树中。
  • 一个节点在根,那么有两种情况:
    • 另一个节点在原来子树中;
    • 另一个节点在新加入的子树中。
  • 两个节点都在根。

转移的时候把路径的首尾接好,设新加入的子树是 \(j\),算好 \(i \rightarrow j\) 这条边经过一次还是两次。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 3010
#define maxm 6010
#define oo 700000000

int n, m, K, head[maxn], nxt[maxm], to[maxm], dist[maxm];

void AddEdge(int a, int b, int c) {
	to[++m] = b; dist[m] = c; nxt[m] = head[a]; head[a] = m;
	swap(a, b);
	to[++m] = b; dist[m] = c; nxt[m] = head[a]; head[a] = m;
	return ;
}

void upd(int& a, int b) {
	if(a > b) a = b;
	return ;
}
int f[maxn][maxn][2][2], siz[maxn];
void dp(int u, int fa) {
	f[u][1][1][1] = 0;
	siz[u] = 1;
	for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa) {
		dp(to[e], u);
		dwn(i, siz[u], 1) dwn(j, siz[to[e]], 1) {
			upd(f[u][i+j][0][0], f[u][i][0][0] + f[to[e]][j][1][1] + (dist[e] << 1));
			upd(f[u][i+j][0][0], min(f[to[e]][j][0][0], min(f[to[e]][j][0][1], f[to[e]][j][1][1])) + f[u][i][1][1] + (dist[e] << 1));
			upd(f[u][i+j][0][0], f[u][i][0][1] + min(f[to[e]][j][0][1], f[to[e]][j][1][1]) + dist[e]);
			upd(f[u][i+j][0][1], f[u][i][0][1] + f[to[e]][j][1][1] + (dist[e] << 1));
			upd(f[u][i+j][0][1], f[u][i][1][1] + min(f[to[e]][j][0][1], f[to[e]][j][1][1]) + dist[e]);
			upd(f[u][i+j][1][1], f[u][i][1][1] + f[to[e]][j][1][1] + (dist[e] << 1));
		}
		siz[u] += siz[to[e]];
	}
	// rep(i, 0, siz[u]) printf("[%d][%d]: %d %d %d\n", u, i, f[u][i][0][0], f[u][i][0][1], f[u][i][1][1]);
	return ;
}

int main() {
	n = read(); K = read();
	rep(i, 1, n - 1) {
		int a = read(), b = read(), c = read();
		AddEdge(a, b, c);
	}
	
	rep(i, 1, n) rep(j, 0, n) rep(s, 0, 1) rep(t, 0, 1) f[i][j][s][t] = oo;
	dp(1, 0);
	
	int ans = oo;
	rep(i, 1, n) upd(ans, min(f[i][K][0][0], min(f[i][K][0][1], f[i][K][1][1])));
	printf("%d\n", ans);
	
	return 0;
}

[Problem B]Tower

试题描述

TwT

QwQ

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

这个题直接上哈希……

由于在首尾添加一个字符,答案只可能加 \(1\) 或加 \(2\),于是分别维护一下首尾正反长度为 \(nowans\)\(nowans + 1\) 的串的哈希值就好了,有必要再开一个串记录当前串长什么样。然后记得把所有东西可持久化一下(包括当前串的首尾指针、答案、\(8\) 种哈希值),其实就是用个栈维护一下,到撤销的时候直接把栈的顶指针减去它撤回的步数即可。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)

const int BufferSize = 1 << 16;
char buffer[BufferSize], *Head, *Tail;
inline char Getchar() {
    if(Head == Tail) {
        int l = fread(buffer, 1, BufferSize, stdin);
        Tail = (Head = buffer) + l;
    }
    return *Head++;
}
int read() {
    int x = 0, f = 1; char c = Getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = Getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = Getchar(); }
    return x * f;
}

#define maxn 10000010
#define UI unsigned int
#define LL long long
#define rate 233

UI rinv;
void gcd(LL a, LL b, LL& x, LL& y) {
	if(!b){ x = 1; y = 0; return ; }
	gcd(b, a % b, y, x); y -= (a / b) * x;
	return ;
}
void getinv() {
	LL x, y;
	gcd(rate, 4294967296ll, x, y);
	rinv = x;
	return ;
}

short S[maxn<<1];
int rpow[maxn], fr[2][2][maxn], ed[2][2][maxn]; // [rev][add]
int hd[maxn], tl[maxn], ans[maxn], top;

void getMore() {
	if(ans[top] + 1 <= tl[top] - hd[top] + 1) {
		ed[0][1][top] = ed[0][0][top] + rpow[ans[top]] * S[tl[top]-ans[top]];
		ed[1][1][top] = ed[1][0][top] * rate + S[tl[top]-ans[top]];
		fr[0][1][top] = fr[0][0][top] * rate + S[hd[top]+ans[top]];
		fr[1][1][top] = fr[1][0][top] + rpow[ans[top]] * S[hd[top]+ans[top]];
	}
	else rep(rev, 0, 1) ed[rev][1][top] = fr[rev][1][top] = 0;
	return ;
}

void addr(short c) {
	top++;
	S[tl[top] = tl[top-1]+1] = c;
	hd[top] = hd[top-1];
	if(ans[top-1] + 1 <= tl[top-1] - hd[top-1] + 1 && ed[0][1][top-1] * rate + c == ed[1][1][top-1] + rpow[ans[top-1]+1] * c) { // ans + 2
		ans[top] = ans[top-1] + 2;
		ed[0][0][top] = ed[0][1][top-1] * rate + c;
		ed[1][0][top] = ed[1][1][top-1] + rpow[ans[top-1]+1] * c;
		fr[0][0][top] = fr[0][1][top-1] * rate + S[hd[top-1]+ans[top-1]+1];
		fr[1][0][top] = fr[1][1][top-1] + rpow[ans[top-1]+1] * S[hd[top-1]+ans[top-1]+1];
	}
	else if(ed[0][0][top-1] * rate + c == ed[1][0][top-1] + rpow[ans[top-1]] * c) { // ans + 1
		ans[top] = ans[top-1] + 1;
		ed[0][0][top] = ed[0][0][top-1] * rate + c;
		ed[1][0][top] = ed[1][0][top-1] + rpow[ans[top-1]] * c;
		fr[0][0][top] = fr[0][0][top-1] * rate + S[hd[top]+ans[top-1]];
		fr[1][0][top] = fr[1][0][top-1] + rpow[ans[top-1]] * S[hd[top]+ans[top-1]];
	}
	else {
		ans[top] = ans[top-1];
		ed[0][0][top] = ed[0][0][top-1] * rate + c - rpow[ans[top]] * S[tl[top]-ans[top]];
		ed[1][0][top] = (ed[1][0][top-1] + rpow[ans[top]] * c - S[tl[top]-ans[top]]) * rinv;
		fr[0][0][top] = fr[0][0][top-1];
		fr[1][0][top] = fr[1][0][top-1];
	}
	getMore();
	return ;
}

void addl(short c) {
	top++;
	S[hd[top] = hd[top-1]-1] = c;
	tl[top] = tl[top-1];
	if(ans[top-1] + 1 <= tl[top-1] - hd[top-1] + 1 && fr[0][1][top-1] + rpow[ans[top-1]+1] * c == fr[1][1][top-1] * rate + c) { // ans + 2
		ans[top] = ans[top-1] + 2;
		fr[0][0][top] = fr[0][1][top-1] + rpow[ans[top-1]+1] * c;
		fr[1][0][top] = fr[1][1][top-1] * rate + c;
		ed[0][0][top] = ed[0][1][top-1] + rpow[ans[top-1]+1] * S[tl[top-1]-ans[top-1]-1];
		ed[1][0][top] = ed[1][1][top-1] * rate + S[tl[top-1]-ans[top-1]-1];
	}
	else if(fr[0][0][top-1] + rpow[ans[top-1]] * c == fr[1][0][top-1] * rate + c) { // ans + 1
		ans[top] = ans[top-1] + 1;
		fr[0][0][top] = fr[0][0][top-1] + rpow[ans[top-1]] * c;
		fr[1][0][top] = fr[1][0][top-1] * rate + c;
		ed[0][0][top] = ed[0][0][top-1] + rpow[ans[top-1]] * S[tl[top]-ans[top-1]];
		ed[1][0][top] = ed[1][0][top-1] * rate + S[tl[top]-ans[top-1]];
	}
	else {
		ans[top] = ans[top-1];
		fr[0][0][top] = (fr[0][0][top-1] + rpow[ans[top]] * c - S[hd[top]+ans[top]]) * rinv;
		fr[1][0][top] = fr[1][0][top-1] * rate + c - rpow[ans[top]] * S[hd[top]+ans[top]];
		ed[0][0][top] = ed[0][0][top-1];
		ed[1][0][top] = ed[1][0][top-1];
	}
	getMore();
	return ;
}

int main() {
	getinv();
	top = 1;
	hd[1] = maxn; tl[1] = maxn - 1; ans[1] = 0;
	rep(rev, 0, 1) rep(add, 0, 1) fr[rev][add][1] = ed[rev][add][1] = 0;
	
	int q = read(), lst = 0; char c = Getchar(); LL sans = 0;
	rpow[0] = 1; rep(i, 1, q + 1) rpow[i] = rpow[i-1] * rate;
	while(!isdigit(c)) c = Getchar();
	rep(i, 1, q) {
		short op = c - '0', x = 0;
		c = Getchar(); x = c - '0'; c = Getchar(); x = x * 10 + c - '0'; if(i < q) c = Getchar();
		x = (x + lst) % 100;
		if(op == 1) addr(x);
		if(op == 2) addl(x);
		if(op == 3) top -= x;
		lst = ans[top];
		// printf("%d %d: %d\n", op, x, lst);
		sans += lst;
	}
	
	printf("%lld\n", sans);
	
	return 0;
}

[Problem C]Alice and Bob VII

试题描述

从前,有 \(n\) 个盒子(编号从 \(1\)\(n\)),\(m\) 把钥匙(编号从 \(1\)\(m\))和 \(d\) 个商店(编号从 \(1\)\(d\))。编号为 \(i\) 的钥匙可以打开编号为 \(a_{i,1},a_{i,2},\cdots,a_{i,k_i}\) 的盒子。但是,一把钥匙一旦打开了一个盒子,它会立刻消失。因此,一把钥匙不能打开多个盒子。编号为 \(i\) 的钥匙只在商店 \(s_i\) 出售,价格为 \(c_i\)
个金币。Alice 不能多次购买同一把钥匙。现在,Alice 想买一些钥匙来打开所有的盒子。

Bob 想来捣乱。他可以在 Alice 决定购买钥匙之前改变某些钥匙的价格。如果 Bob 支付 \(b_j\) 个金币,Bob 就可以将商店 \(j\) 中所有钥匙的价格全部提高 \(1\) 金币。对于每家商店,他可以重复上述过程任意非负整数次。例如:如果他支付 \(2b_j\) 个金币,他可以将商店 \(j\) 中所有钥匙的价格全部提高 \(2\) 金币;但是,他不能支付 \(\frac{b_j}{2}\) 个金币来将价格提高 \(0.5\) 金币。

Alice 的目的是最小化 \((Alice支付的金币 - Bob支付的金币)\),Bob 的目的是最大化该值。假设 Alice 和 Bob 都以最优策略来操作,请你计算最终该值是多少。

如果该值为无穷大,输出 \(-1\)

数据保证如果 Bob 不来捣乱,Alice 是可以打开所有盒子的。

输入

第一行三个整数 \(n,m,d(1 \le n \le 100,1 \le m \le 1000,1 \le n,d \le m)\)

接下来 \(m\) 行,每行表示一把钥匙。每行开头有三个整数 \(c_i,s_i,k_i\) ,分别表示钥匙价格,钥匙出售商店的编号,这把钥匙能打开多少个盒子。接下来 \(k_i\) 个整数,表示这把钥匙能打开的盒子的编号。\((1 \le c_i \le 1000,1 \le s_i \le d,1 \le k_i \le \mathrm{min}(10,n),1 \le a_i,j \le n, and\ if\ j \ne k,a_{i,j} \ne a_{i,k})\)

接下来 \(d\) 行,每行一个整数 \(b_i(1 \le b_i \le 1000)\)

输出

输出一个整数表示答案。

输入示例1

3 4 1
2 1 2 1 2
2 1 2 2 3
2 1 2 3 1
3 1 3 1 2 3
5

输出示例1

6

输入示例2

3 4 1
2 1 2 1 2
2 1 2 2 3
2 1 2 3 1
3 1 3 1 2 3
2

输出示例2

-1

输入示例2

2 3 2
3 1 2 1 2
4 1 1 2
5 2 2 1 2
1
2

输出示例2

8

数据规模及约定

对于 \(30\%\) 的数据,\(1 \le n \le 5, 1 \le m \le 10\)

对于所有数据,\(1 \le n \le 100, 1 \le m \le 1000, 1 \le n,d \le m\)

题解

这是一道处于未知领域的题……要用到拉格朗日乘数法证明它的正确性,但是仔细想一想发现还是证明不了,把原题解丢在这里吧,作为一个未解问题。(或许这题彻头彻尾地错了呢?)

T_T

拉格朗日乘数法和KKT条件

支持向量机:Duality

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 2110
#define maxm 24210
#define oo 2147483647

struct Edge {
	int from, to, flow, cost;
	Edge() {}
	Edge(int _1, int _2, int _3, int _4): from(_1), to(_2), flow(_3), cost(_4) {}
} es[maxm];
struct ZKW {
	int n, m, s, t, cost, ans, head[maxn], nxt[maxm];
	Edge es[maxm];
	int d[maxn], Q[maxn], hd, tl;
	bool inq[maxn];
	bool vis[maxn];
	
	void init() {
		m = 0; memset(head, -1, sizeof(head));
		return ;
	}
	void setn(int _) {
		n = _;
		return ;
	}
	
	void AddEdge(int a, int b, int c, int d) {
		es[m] = Edge(a, b, c, d); nxt[m] = head[a]; head[a] = m++;
		es[m] = Edge(b, a, 0, -d); nxt[m] = head[b]; head[b] = m++;
		return ;
	}
	
	int Nxt(int x) { return (x + 1) % maxn; }
	bool BFS() {
		rep(i, 1, n) d[i] = oo;
		d[t] = 0;
		hd = tl = 0; Q[tl = Nxt(tl)] = t; inq[t] = 1;
		while(hd != tl) {
			int u = Q[hd = Nxt(hd)]; inq[u] = 0;
			for(int i = head[u]; i != -1; i = nxt[i]) {
				Edge& e = es[i^1];
				if(e.flow && d[e.from] > d[u] + e.cost) {
					d[e.from] = d[u] + e.cost;
					if(!inq[e.from]) inq[e.from] = 1, Q[tl = Nxt(tl)] = e.from;
				}
			}
		}
		if(d[s] == oo) return 0;
		cost = d[s];
		return 1;
	}
	
	int DFS(int u, int a) {
		if(u == t || !a) return ans += cost * a, a;
		if(vis[u]) return 0;
		vis[u] = 1;
		int flow = 0, f;
		for(int i = head[u]; i != -1; i = nxt[i]) {
			Edge& e = es[i];
			if(d[e.to] == d[u] - e.cost && (f = DFS(e.to, min(a, e.flow)))) {
				flow += f; a -= f;
				e.flow -= f; es[i^1].flow += f;
				if(!a) return flow;
			}
		}
		return flow;
	}
	
	int MaxFlow(int _s, int _t) {
		s = _s; t = _t;
		int flow = 0, f;
		while(BFS())
			do {
				memset(vis, 0, sizeof(vis));
				f = DFS(s, oo);
				flow += f;
			} while(f);
		return flow;
	}
} sol;

int CntP;
struct Point {
	int id;
	Point(): id(0) {}
	int p() { return id ? id : id = ++CntP; }
} box[maxn], key[maxn], shop[maxn], S, T;
vector <int> idm[maxn];
int addcost[maxn];

int main() {
	int bx = read(), ky = read(), shp = read();
	sol.init();
	rep(i, 1, ky) {
		int pri = read(), sid = read(), k = read();
		idm[sid].push_back(sol.m);
		sol.AddEdge(shop[sid].p(), key[i].p(), 1, pri);
		rep(j, 1, k) sol.AddEdge(key[i].p(), box[read()].p(), 1, 0);
	}
	rep(i, 1, shp) sol.AddEdge(S.p(), shop[i].p(), addcost[i] = read(), 0);
	rep(i, 1, bx) sol.AddEdge(box[i].p(), T.p(), 1, 0);
	sol.setn(CntP);
	
	if(sol.MaxFlow(S.p(), T.p()) < bx) return puts("-1"), 0;
	printf("%d\n", sol.ans);
	
	return 0;
}

推荐阅读