【总结】BalticOI 2018
2021/7/17
T1:路径
挺好一题,不知道为什么保证答案 \(\le 10^{18}\)。
由于路径上的颜色互不相同,所以每一次必须走到一个没有走过的颜色,所以路径长度 \(\le K\)。
同时 \(K\) 非常小,我们直接状压 DP,\(f[i][S]\) 表示以 \(i\) 结尾,状态为 \(S\) 的方案数即可,时间复杂度 \(\mathcal{O}((N+M)2^K)\)。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 300005
using namespace std;
int n, m, k, c[N];long long f[32][N];
vector<int>e[N];
int main(){
scanf("%d%d%d", &n, &m, &k);
rep(i, 1, n)scanf("%d", &c[i]);
rep(i, 1, m){
int x, y;
scanf("%d%d", &x, &y);
e[x].push_back(y),
e[y].push_back(x);
}
rep(i, 1, (1 << k) - 1){
if(i == (i & - i)){
rep(x, 1, n)if((1 << (c[x] - 1)) == i)f[i][x] = 1;
}
else{
rep(x, 1, n){
int y = 1 << (c[x] - 1);
if(y & i){
for(int t : e[x])f[i][x] += f[i ^ y][t];
}
}
}
}
long long ans = 0;
rep(i, 1, (1 << k) - 1)if(i != (i & -i))
rep(j, 1, n)ans += f[i][j];
printf("%lld\n",ans);
return 0;
}
T2 :基因工程
刚开始看非常没思路,只会 \(N^3\) 朴素,先跳了。
后来看发现还是只会 \(N^3\) 暴力。。。有两个 \(Sub\) 的字符集 \(=2\) ,我们直接看成 \(0/1\) 串,显然不同的位置就是两个数异或后 \(1\) 的个数,可以 bitset 优化做到 \(\mathcal{O}(\dfrac{N^3}{w})\)。测了一下发现 \(N\le 4\times 10^3\) 能卡进 \(1.5s\) 就交了。
最后一个 \(Sub\) 字符集 \(=4\) ,感觉和 \(=2\) 没有什么区别,如果把一个字符压到两位里也可以做,大概 \(4\) 倍常数被卡死了。
写了一下发现直接计算相等的位置好写一些,我们对于字符 \(A/C/T/G\) 分别统计相同位置,对于一个字符,将对应的位置置为 \(1\) ,那么相等个数就是两个数按位与后的 \(1\) 个数。剪枝一下,我们只用枚举 \(i<j\) 的两个串,发现一个串合法直接返回即可。
手造数据测了下发现卡不进 \(2s\) ,同时常数波动挺大的。显然答案串越靠前计算量越小,但我们又不知道答案串在哪,直接随机一个顺序,答案串期望在中间,其他的就看脸了(
赛时没开 -O2,开榜发现 TLE 一大片。开 -O2 再选个好的种子就能卡过去了(
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = a;i <= b;i++)
#define pre(i, a, b) for(int i = a;i >= b;i--)
using namespace std;
#define N 4105
int n, m, k, v[N], op, p[N];char s[N][N];
bitset<N - 5>a[N], b[N], c[N], d[N];
inline bool ask(int x,int y){
if(!op)return (a[x] & a[y]).count() + (b[x] & b[y]).count() == m - k;
return (a[x] & a[y]).count() + (b[x] & b[y]).count() + (c[x] & c[y]).count() + (d[x] & d[y]).count() == m - k;
}
char buf[1<<22],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
int main(){
srand(time(0));
n = read(), m = read(), k = read();
rep(i, 1, n){
char ch = getchar();
while(ch < 'A' || ch > 'Z')ch = getchar();
rep(j, 0, m - 1){
if(ch == 'A')a[i][j] = 1;
else if(ch == 'C')b[i][j] = 1;
else if(ch == 'T')c[i][j] = 1, op = 1;
else d[i][j] = 1, op = 1;
ch = getchar();
}
}
rep(i, 1, n)p[i] = i;
random_shuffle(p + 1, p + n + 1);
rep(i, 1, n){
rep(j, i + 1, n)if(!ask(p[i], p[j]))v[i] = v[j] = 1;
if(!v[i]){printf("%d\n",p[i]);return 0;}
}
return 0;
}
T3:多角恋
有意思的思维题。
首先直接将字符串转化为数,这就是一个 \(N=M\) 的有向图。
显然 \(N\) 为奇数无解,因为我们要两两配对。
再想一下发现当一个点有两个出边时无解,因为我们只能改变终点。
对于其他情况,我们任意指定一个配对,然后修改终点即可。因此上面有解的条件是充分的。
现在要求最少花费的代价,转化为求最多能保留多少条边。
对于第 \(2\) 个 \(Sub\) ,整个图是若干个环,我们计算每个环的大小,显然一个大小为 \(n\) 的环最多保留 \(\left\lfloor\dfrac{n}{2}\right\rfloor\) 条边。
对于第 \(3\) 个 \(Sub\) ,整个图是若干个链,我们计算每个链的大小,显然一个大小为 \(n\) 的链最多保留 \(\left\lfloor\dfrac{n}{2}\right\rfloor\) 条边。
构造方法大概是选一条边再不选一条边,依次类推。
猜到对于任意一个图,就是求最大匹配,即任意两条保留边不能有公共点。
这张图同时是基环内向树,破环成链可以直接跑 DP。时间复杂度 \(\mathcal{O}(N)\)。
到这里还过不去样例(良心出题人),我们还要特判大小为 \(2\) 的环。
赛时写错一个细节挂了一个点(惨,下面是赛后代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
using namespace std;
int n, idx, v[N], f[N][2], ans, p[N];
map<string, int>h;
vector<int>e[N];
int bx, by;
void dfs(int x){
v[x] = 1;
f[x][0] = f[x][1] = 0;
for(int y : e[x]){
if(x == bx && y == by)continue;
dfs(y);
f[x][1] = max(f[x][1] + max(f[y][1], f[y][0]), f[x][0] + f[y][0] + 1);
f[x][0] += max(f[y][0], f[y][1]);
}
}
void dfs2(int x){
v[x] = 1;
f[x][0] = f[x][1] = 0;
for(int y : e[x]){
if(x == bx && y == by)continue;
dfs2(y);
if(x != bx && x != by){
f[x][1] += max(f[y][1], f[y][0]);
if(y != bx && y != by)f[x][1] = max(f[x][1], f[x][0] + f[y][0] + 1);
}
f[x][0] += max(f[y][0], f[y][1]);
}
}
void calc(int x){
while(!v[x])v[x] = 1, x = p[x];
bx = p[x], by = x;
if(x == p[x])dfs(x), ans += max(f[x][0], f[x][1]);
else if(x == p[p[x]]){
dfs2(x);
ans += max(f[x][0], f[x][1]) + 2;
}
else{
dfs(x);
int cur = max(f[x][0], f[x][1]);
dfs2(x);
cur = max(cur, max(f[x][0], f[x][1]) + 1);
ans += cur;
}
}
int main(){
ios::sync_with_stdio(false);
cin >> n;
if(1 & n){puts("-1");return 0;}
rep(i, 1, n){
string a, b;
cin >> a >> b;
if(!h.count(a))h[a] = ++idx;
if(!h.count(b))h[b] = ++idx;
int x = h[a], y = h[b];
if(p[x]){cout<< "-1" <<endl;return 0;}
p[x] = y, e[y].push_back(x);
}
rep(i, 1, n)if(!v[i])calc(i);
cout<< n - ans << endl;
return 0;
}
T4:火星人的 DNA
比前两题简单多了。
求最短子串使得一些字符出现次数大于某个值。
显然我固定右端点,左端点的最大值递增,直接双指针即可。
对于每个限制,我们用 \(set\) 动态维护即可。时间复杂度 \(\mathcal{O}(N\log N)\)。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 200005
using namespace std;
int n, k, m, u[N], v[N], col[N];
multiset<pair<int,int> >s;
int main(){
scanf("%d%d%d", &n, &k, &m);
rep(i, 1, n)scanf("%d", &u[i]);
rep(i, 1, m){
int x, y;
scanf("%d%d", &x, &y);
v[x] = 1, col[x] = y, s.insert(make_pair(y, x));
}
int ans = n + 1, j = 1;
rep(i, 1, n){
if(v[u[i]]){
s.erase(make_pair(col[u[i]], u[i]));
col[u[i]]--;
s.insert(make_pair(col[u[i]], u[i]));
while(j <= i && (!v[u[j]] || col[u[j]] < 0)){
if(v[u[j]]){
s.erase(make_pair(col[u[j]], u[j]));
col[u[j]]++;
s.insert(make_pair(col[u[j]], u[j]));
}
j++;
}
if(j <= i && (*s.rbegin()).first <= 0)ans = min(ans, i - j + 1);
}
}
if(ans == n + 1)puts("impossible");else printf("%d\n", ans);
return 0;
}