【题解】USACO 2023 January Sliver
因为 Glod 打寄了,就来写写 Sliver 的题解吧,现在应该比赛结束了吧。
A.Find And Replace
题目分析:
我们可以对给定的串建出一种关系,也就是如果在上面的字符串中是字符 \(c_1\) 在下面的字符串对应的位置是字符 \(c_2\),那么我们就连边 \((c_1,c_2)\) 代表我们要将 \(c_1\) 连向变成 \(c_2\)。
可以发现如果某一个点的出度大于 \(1\) 那么一定无解,因为我们需要将这个字符变成多个字符,但是我们只能将这个字符变成最多一种。
这样建出来的图是只含有三种结构的:链、环、挂着链的环
对于链的处理很简单,就是沿着链的方向倒着处理一下就好了。
对于环显然就需要断环成链,也就是找到一个环上的点,把它和下一个点断开,也就是将这个点换成一个其他的字符 \(c_3\),然后从 \(c_3\) 再连向这个环上的点的下一个点,也就是将环变了的链,但是这样做需要有一个字符 \(c_3\) 它的出度为 \(0\)。
对于环加链其实和环是一样的,只不过我们这次找的 \(c_3\) 就可以直接是与环直接相连的链上的点,这样只需要一次操作就可以同时实现链上的点和我们断掉的点的需要,节省了一次操作。而且这样本质上是断环成链所以可以多出来一个出度为 \(0\) 的点。
这样其实代码不好写,因为找环很麻烦,但是环一定是一个强连通分量,所以直接 \(tarjan\) 缩点之后就变得很简单了。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3+5;
struct edge{
int nxt,to;
edge(){}
edge(int _nxt,int _to){
nxt = _nxt,to = _to;
}
}e2[N];
int top,tot,col,st[N],dfn[N],sz[N],low[N],color[N],chu[N],deg[N];
char s[200*N],t[200*N];
bool a[100][100],flag[N];
int get(char c){
if(c >= 'a' && c <= 'z') return c - 'a' + 1;
return c - 'A' + 1 + 26;
}
void tarjan(int now){
st[++top] = now;dfn[now] = low[now] = ++tot;
for(int i=1; i<=52; i++){
if(now == i) continue;
if(a[now][i]){
int to = i;
if(!dfn[to]){
tarjan(to);
low[now] = min(low[now],low[to]);
}
else if(!color[to]) low[now] = min(low[now],dfn[to]);
}
}
if(dfn[now] == low[now]){
++col;
color[now] = col;sz[col]++;
while(st[top] != now){
color[st[top]] = col;sz[col]++;
top--;
}
top--;
}
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int T;scanf("%d",&T);
while(T--){
bool tag1 = true,tag2 = false;
scanf("%s",s+1);scanf("%s",t+1);
int n = strlen(s+1);
for(int i=1; i<=n; i++) a[get(s[i])][get(t[i])] = true;
for(int i=1; i<=52; i++){
for(int j=1; j<=52; j++){
if(a[i][j]) chu[i]++;
}
}
for(int i=1; i<=52; i++){
if(chu[i] > 1) tag1 = false;
else if(chu[i] == 0) tag2 = true;
}
memset(chu,0,sizeof(chu));
for(int i=1; i<=52; i++)
if(!dfn[i])
tarjan(i);
for(int i=1; i<=52; i++) if(a[i][i]) flag[color[i]] = true;
for(int i=1; i<=52; i++){
for(int j=1; j<=52; j++){
if(i == j) continue;
if(a[i][j] && color[i] != color[j]) chu[color[i]]++,deg[color[j]]++;
}
}
int ans = 0,tmp = 0;
for(int i=1; i<=col; i++) if(chu[i] == 1) ans++;
for(int i=1; i<=col; i++){
if(deg[i] == 0 && sz[i] > 1){
tmp += sz[i] + 1;
}
else if(deg[i] != 0 && sz[i] > 1){
tag2 = true,ans += sz[i];
}
}
if(!tag2 && tmp > 0) tag1 = false;
if(tag2) ans += tmp;
if(!tag1) printf("-1\n");
else printf("%d\n",ans);
memset(a,false,sizeof(a));memset(deg,0,sizeof(deg));memset(chu,0,sizeof(chu));
memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));memset(st,0,sizeof(st));
memset(color,0,sizeof(color));memset(flag,false,sizeof(flag));memset(sz,0,sizeof(sz));
}
return 0;
}
B.Following Directions
题目分析:
可以发现对于一次修改我们影响的点最多 \(O(n)\) 个,所以只需要从我们修改的点开始 \(dfs\) 将原来的线路的贡献删除,将新线路的贡献加上即可。
对于贡献的初值就不能每个点直接 \(dfs\) 了,这样的复杂度为 \(O(n^3)\),所以就用一下拓扑排序就好了,因为我们的贡献显然是可以累积去传递的。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1505;
int n,cnt[N][N],val[N][N],deg[N][N];
char s[N][N];
void get(int x,int y){
cnt[x][y]++;
if(x > n || y > n) return;
if(s[x][y] == 'R') get(x,y+1);
else get(x+1,y);
}
void del(int x,int y,int val){
cnt[x][y] -= val;
if(x > n || y > n) return;
if(s[x][y] == 'R') del(x,y+1,val);
else del(x+1,y,val);
}
void add(int x,int y,int val){
cnt[x][y] += val;
if(x > n || y > n) return;
if(s[x][y] == 'R') add(x,y+1,val);
else add(x+1,y,val);
}
int get_ans(){
int res = 0;
for(int i=1; i<=n; i++) res += cnt[n+1][i] * val[n+1][i] + cnt[i][n+1] * val[i][n+1];
return res;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d",&n);
for(int i=1; i<=n; i++){
scanf("%s",s[i]+1);
scanf("%d",&val[i][n+1]);
}
for(int i=1; i<=n; i++) scanf("%d",&val[n+1][i]);
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
if(s[i][j] == 'R') deg[i][j+1]++;
else deg[i+1][j]++;
}
}
queue<pair<int,int> > q;
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
if(deg[i][j] == 0){
q.push({i,j});
}
}
}
while(!q.empty()){
int x = q.front().first,y = q.front().second;q.pop();
if(x > n || y > n) continue;
cnt[x][y]++;
if(s[x][y] == 'R'){
deg[x][y+1]--;cnt[x][y+1] += cnt[x][y];
if(deg[x][y+1] == 0) q.push({x,y+1});
}
else{
deg[x+1][y]--;cnt[x+1][y] += cnt[x][y];
if(deg[x+1][y] == 0) q.push({x+1,y});
}
}
int ans = get_ans();
printf("%d\n",ans);
int Q;scanf("%d",&Q);
while(Q--){
int x,y;scanf("%d%d",&x,&y);
int tmp = cnt[x][y];
del(x,y,tmp);
if(s[x][y] == 'R') s[x][y] = 'D';
else s[x][y] = 'R';
add(x,y,tmp);
ans = get_ans();
printf("%d\n",ans);
}
return 0;
}
C.Moo Route
题目分析:
因为我们如果要走过某一条边必然一来一回,所以可以预先减掉 \(2\),剩下的部分就是我们用反复横跳来消耗的。
为了使得转向次数最少,有一种显然地构造方式就是我们每一次都跳一个极长的区间,然后转向回到我们当前点,直到相对应的边不能再反复横跳就去下一个点。
这样做显然是正确的,因为如果没有跳极长的区间或提前去下一个点都是不会更优的。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int a[N];
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n;scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d",&a[i]),a[i] -= 2;
for(int i=1; i<=n; i++) printf("R");
for(int i=n; i>=1; i--){
if(a[i] == 0) printf("L");
else{
while(a[i] != 0){
int pos = i;
for(;a[pos] > 0; pos--);
pos++;
for(int j=pos; j<=i; j++) a[j] -= 2;
for(int j=pos; j<=i; j++) printf("L");
for(int j=pos; j<=i; j++) printf("R");
}
printf("L");
}
}
return 0;
}