2022 纪中集训 7.11 笔记
T1 GMOJ 3238./COCI2013 超空间旅行
设 \(dis[i][j]\) 从S走到i点时用了j条x边。
先将 \(x\) 视作为 \(0\) 跑 SPFA。
得到 \(n\) 条直线, 表示为 \(y = ix + dis[T][i]\)。
如图所示,点 \(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集训队出题】最大收益
最开始想的是用费用流来做,但我不是很熟练,打了很久,而且复杂度似乎不正确。
下楼做核酸的时候突发奇想,能不能确定那个每个区间到底用哪个点,然后用那些点和工作做二分图匹配?
回来之后就发现是可行的,火速码完。
但交上去却爆蛋了。。。
原来是有一个地方把变量名打错了。。。
论文
提取码: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. 单词争霸
将 \(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)\)。
可能的后继局面有两种:
- 选取的是 \(i\),这样,后继局面的 \(\text{SG}\) 是 \(allson\)。
- 选取的是 \(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. 数据读取问题
不会,挖坑待补。。。