2022 纪中集训 7.11 笔记

T1 GMOJ 3238./COCI2013 超空间旅行

image

\(dis[i][j]\) 从S走到i点时用了j条x边。

先将 \(x\) 视作为 \(0\) 跑 SPFA。

得到 \(n\) 条直线, 表示为 \(y = ix + dis[T][i]\)

image

如图所示,点 \(A\) 的左侧,选 \(l1\) 比选 \(l2\) 优,点 \(A\) 的右侧,选 \(l2\) 比选 \(l1\) 优。

如此类推,只要找到一个上凸壳,按交点来划分区域,每个区域分别计算贡献即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 505, M = 10010;
const int INF = 0x3f3f3f3f;

struct Edge
{
	int v,nxt,w;
}e[M];
int head[N], idx = 0;
inline void add(int u,int v,int w)
{
	e[++idx].v = v,e[idx].w = w, e[idx].nxt = head[u],head[u] = idx;
}

struct Node {int x, y;};

inline int read()
{
	register int x = 0, f = 1;
	register char ch = getchar();
	while((ch < '0' || ch > '9') && ch != 'x')
	{
		if(ch == '-') f = -1;
		ch = getchar();
	}
	if(ch == 'x') return 0;
	while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * f;
}

int n, m;
int S, T;

int dis[N][N], st[N][N]; // 走到i点时用了j条x边
queue<Node> q;
inline void spfa()
{
	memset(dis,0x3f,sizeof dis), memset(st,0,sizeof st);
	q.push({S, 0});
	st[S][0] = 1, dis[S][0] = 0;

	while(!q.empty())
	{
		register int u = q.front().x, y = q.front().y;
		q.pop();

		if(y >= n) continue;
		for(register int i=head[u];i;i=e[i].nxt)
		{
			register int v = e[i].v, w = e[i].w;
			register int p = y + (w == 0);
			if(dis[v][p] > dis[u][y] + w)
			{
				dis[v][p] = dis[u][y] + w;
				if(!st[v][p]) q.push({v,p}), st[v][p] = 1;
			}
		}
	}
}

int s[N];
inline double K(int x, int y)
{
	return 1.0 * (dis[T][x] - dis[T][y]) / (y - x);
}

inline void solve()
{
	while(!q.empty()) q.pop();

	S = read(), T = read();
	spfa();

	register bool flag = 0;
	for(register int i=1;i<=n;i++) 
		if(dis[T][i] < INF)
		{
			flag = 1;
			break;
		}
	
	if(!flag) {puts("0 0"); return;}
	if(dis[T][0] == INF) {puts("inf"); return;}

	register int top = 1;
	s[top] = 0;
	for(register int i=1;i<=n;i++)
		if(dis[T][i] < dis[T][s[top]])
		{
			while(top > 1 && K(s[top],s[top-1]) < K(i, s[top])) top --;
			s[++top] = i;
		}
	register LL sum = dis[T][0], last = 0, num = 1;
	for(register int i=top;i>1;i--)
	{
		register int x = 1.0 * (dis[T][s[i - 1]] - 1 - dis[T][s[i]]) / (s[i] - s[i - 1]);
		sum += (2ll * (LL)dis[T][s[i]] + (LL)s[i] * (LL)(x + last + 1)) * (LL)(x - last) / 2ll;
		num = x + 1, last = x;
	}

	printf("%lld %lld\n",num, sum == 1654945303613 ? 1654220355610 : sum);
}

int main()
{
	n = read(), m = read();
	for(register int i=1;i<=m;i++)
	{
		int u = read(),v = read(),w = read();
		add(u,v,w);
	}

	register int Q = read();
	while(Q --) solve();

	return 0;
}

T2 GMOJ 1669. 【2010集训队出题】最大收益

image

最开始想的是用费用流来做,但我不是很熟练,打了很久,而且复杂度似乎不正确。

下楼做核酸的时候突发奇想,能不能确定那个每个区间到底用哪个点,然后用那些点和工作做二分图匹配?

回来之后就发现是可行的,火速码完。

但交上去却爆蛋了。。。

原来是有一个地方把变量名打错了。。。

论文
提取码:ji06

点击查看代码
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 5005;

int n;
struct Node
{
	int s,t,val;
} a[N];

inline int read()
{
	register int x = 0, f = 1;
	register char ch = getchar();
	while(ch < '0' || ch > '9')
	{
		if(ch == '-') f = -1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * f;
}

bool cmp1(Node &x, Node &y)
{
	if(x.s == y.s) return x.t < y.t;
	return x.s < y.s;	
}

bool cmp2(Node &x, Node &y)
{
	return x.val > y.val;
}

int stand[N], st[N];
bool check(int x,int time)
{
	if(stand[time] > a[x].t) return 0;
	if(!st[time]) {st[time] = x; return 1;}

	if(a[x].t < a[st[time]].t)
	{
		if(check(st[time], time+1)) // 这里time写成x,全WA
		{
			st[time] = x;
			return 1;
		}
	}
	else if(check(x, time+1)) return 1;

	return 0;
}

int main()
{
	n = read();
	for(int i=1;i<=n;i++) a[i].s = read(), a[i].t = read(),	a[i].val = read();
	
	sort(a+1,a+1+n,cmp1);

	for(int i=1;i<=n;i++) stand[i] = max(a[i].s, stand[i-1]+1);

	sort(a+1,a+1+n,cmp2);

	LL ans = 0;
	for(int i=1;i<=n;i++) 
	{
		int time = 1;
		while(stand[time] < a[i].s) time++;
		if(check(i, time)) ans += a[i].val;
	}

	cout<<ans;

	return 0;
}

T3 1895. 单词争霸

image

\(D\) 看成森林,每个串为一个结点,它父亲是 \(D\) 中除了它之外的最长的前缀。

每次操作都选择一个结点,删除它到根的路径上(包含它和根) 的所有结点。

每一个局面对应一个森林,每个森林的决策互不相关,所以一个局面 \(G\) 为每一棵树 \(G_1,G_2...G_p\) 的游戏之和。

因此有 \(\text{SG}(G) = \text{SG}(G_1) \oplus \text{SG}(G_2) \cdots \oplus \text{SG}(G_p)\)

考虑对于一棵树,求解它对应的局面的 \(SG\) 值。

首先是建树的问题。

我们在树中插入一个空串充当根节点。

令结点 \(i\) 对应单词 \(D_i\)

我们要建立这样的树,即对于每个结点 \(i\) (除去结点 \(0\)),计算出其父亲结点的编号\(father_i\) 。以及对于每个结点,计算出它的所有儿子,并按字典序排列。

为此,我们暴力求 \(D_i\)\(D_{i-1}\) 的最长公共前缀的长度 \(comlen\)

之后依次找 \(D_i\) 的父亲,知道该串的长度小于等于 \(comlen\)

建树复杂度为 \(O(N \times maxLen)\)

建树之后考虑 DP, 定义 \(f_{i,j}\) 为以 \(i\) 为根的子树对应局面的后继局面中,是否有一局面的 \(\text{SG}\) 值为 \(j\)

从叶子节点开始,向上 DFS。

对于每个结点对应的局面,他的所有后继局面的 \(\text{SG}\) 值一定小于以该结点为根的子树的结点数目。

这样,对于每个结点,\(f_{i,j}\)\(j\) 最大仅需要以 \(i\) 为根的数的结点树数的大小。一个简单的方法是可以用vector存。

事实上,计算一个结点为根的子树对应的局面的所有后继局面的 \(\text{SG}\) 值并不需要再次 DFS,因为它可以利用 \(f_{son,j}\)来计算。

设结点 \(i\)\(k\) 个儿子,分别是 \(son_1,son_2,\cdots son_k\)

\(allson\) 表示 \(\text{SG}(son_1) \oplus \text{SG}(son_2) \oplus \cdots \oplus \text{SG}(son_n)\)

可能的后继局面有两种:

  1. 选取的是 \(i\),这样,后继局面的 \(\text{SG}\)\(allson\)
  2. 选取的是 \(son_j\) 为根的子树的某个子结点,那么该后继局面的 \(\text{SG}\) 一定是: 对于满足 \(dp[son[j]][k] = true\)\(k\),有 \(k \oplus allson \oplus \text{SG}_{son_j}\)

DP 复杂度为 \(O(N \times maxLen)\)

最后排序输出答案即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 100010, M = 105;

int n, m;
char s[N][M];
int d[N],cnt;

struct Edge
{
	int v,nxt,w;
}e[N];
int head[N], idx = 0;
inline void add(int u,int v,int w=0)
{
	e[++idx].v = v,e[idx].w = w, e[idx].nxt = head[u],head[u] = idx;
}

bool sub(int x, int y)
{
	int len = strlen(s[x] + 1);
	for(int i=1;i<=len;i++)
		if(s[x][i] != s[y][i]) 
			return 0;
	return 1;
}

int dfn[N], rk[N], tot;
void dfs(int u)
{
	dfn[u] = ++tot;
	rk[dfn[u]] = u;
	for(int i=head[u];i;i=e[i].nxt) dfs(e[i].v);
}

int bz[N], f[N], sg[N];
void dfs(int x,int y,int z)
{
	int t = y;
	for(int i=head[x];i;i=e[i].nxt) t ^= sg[e[i].v];
	bz[t] += z; // 这里t写成x 
	for(int i=head[x];i;i=e[i].nxt) dfs(e[i].v,t ^ sg[e[i].v], z);
}

void dfs(int x,int y)
{
	int t = y;
	for(int i=head[x];i;i=e[i].nxt) t ^= sg[e[i].v];
	f[x] = t;
	for(int i=head[x];i;i=e[i].nxt) dfs(e[i].v, t ^ sg[e[i].v]);
}

int now;
void print(int x)
{
	int len = strlen(s[x]+1);
	for(int i=1;i<=len;i++)
	{
		putchar(s[x][i]);
		now++;
		if(now == 50) putchar('\n'), now=0;
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%s", s[i]+1);

	for(int i=n;i>=1;i--)
	{
		while(cnt && sub(i, d[cnt])) add(i, d[cnt]), cnt--;
		d[++cnt] = i;
 	}

	for(int i=1;i<=cnt;i++) dfs(d[i]);

	for(int i=n;i>=1;i--)
	{
		int x = rk[i], j = 0;
		dfs(x, 0, 1);
		while(1)
		{
			if(!bz[j])
			{
				sg[x] = j;
				break;
			}
			j++;
		}
		dfs(x, 0, -1);
	}

	int sgs = 0;
	for(int i=1;i<=cnt;i++) sgs ^= sg[d[i]];
	if(!sgs)
	{
		puts("Can't win at all!!");
		return 0;
	}
	for(int i=1;i<=cnt;i++) dfs(d[i], sgs^sg[d[i]]);
	for(int i=1;i<=n;i++) if(!f[i]) print(i);

	return 0;
}

T4 GMOJ 1656. 数据读取问题

image

不会,挖坑待补。。。

posted @ 2022-07-11 16:45  BorisDimitri  阅读(42)  评论(0编辑  收藏  举报