Loading

【总结】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;
}
posted @ 2021-07-17 12:05  7KByte  阅读(236)  评论(0编辑  收藏  举报