NOI.ac2020省选模拟赛3

比赛链接

A.SA

problem

给出一个字符串S,然后有Q次询问,每次询问给出一个字符串T,对于每个询问需要回答:S中有多少个连续子串与T恰好有一个字符不相同。

\(|S|,\sum|T| \le 5\times 10^5\)

solution

先对于S建出后缀数组,然后对于一个询问T。枚举T的一个位置i,然后枚举一下把这个位置改成另外的一个字符c。下面就是在后缀数组中查询新的字符串出现过多少次了。

通过后缀数组可以一个函数\(f(L,R,l,r,len)\)表示在sa中位于区间\([L,R]\)的后缀长度为len的子串,其终点位于原字符串的[l,r]位置的后缀,显然这是一个区间。

然后我们查询这个新的字符串,可以通过查询\([1,i]\)出现的区间和\([i+1,|T|]\)出现的区间,然后合并一下,计算所求的个数。

太难描述了,看代码吧

code

/*
* @Author: wxyww
* @Date:   2020-06-03 08:36:02
* @Last Modified time: 2020-06-03 22:05:14
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 500010;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
int n,sa[N],c[N],x[N],m,rk[N],y[N];
char s[N];
void get_sa() {
	for(int i = 1;i <= n;++i) c[x[i] = s[i]]++;
	for(int i = 1;i <= m;++i) c[i] += c[i - 1];
	for(int i = n;i >= 1;--i) sa[c[x[i]]--] = i;

	for(int k = 1;k <= n;k <<= 1) {
		int num = 0;
		for(int i = n - k + 1;i <= n;++i) y[++num] = i;
		for(int i = 1;i <= n;++i) if(sa[i] > k) y[++num] = sa[i] - k;
		for(int i = 2;i <= m;++i) c[i] = 0;
		for(int i = 1;i <= n;++i) ++c[x[i]];
		for(int i = 1;i <= m;++i) c[i] += c[i - 1];
		for(int i = n;i >= 1;--i) sa[c[x[y[i]]]--] = y[i];

		swap(x,y);
		num = 0;
		x[sa[1]] = ++num;
		for(int i = 2;i <= n;++i) {
			if(y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) x[sa[i]] = num;
			else x[sa[i]] = ++num;
		}
		if(num == n) break;
		m = num;
	}
	for(int i = 1;i <= n;++i) rk[sa[i]] = i;
}

void get(int L,int R,int LL,int RR,int &x,int &y,int len) {
	int l = L,r = R;

	x = l,y = r;
	if(L > R) return;
	x = R + 1,y = L - 1;

	while(l <= r) {
		int mid = (l + r) >> 1;
		if(rk[sa[mid] + len] >= LL) x = mid,r = mid - 1;
		else l = mid + 1;
	}
	l = L,r = R;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(rk[sa[mid] + len] <= RR) y = mid,l = mid + 1;
		else r = mid - 1;
	}
}
char t[N];
int L[N],R[N],Lc[N],Rc[N];
int main() {
	scanf("%s",s + 1);
	n = strlen(s + 1);
	m = 'z';
	get_sa();
	for(int i = 'a';i <= 'z';++i) Lc[i] = n,Rc[i] = 1;

	for(int i = 1;i <= n;++i) {
		Lc[s[sa[i]]] = min(Lc[s[sa[i]]],i);
		Rc[s[sa[i]]] = max(Rc[s[sa[i]]],i);
	}

	int T = read();
	while(T--) {
		scanf("%s",t + 1);
		int mm = strlen(t + 1);
		ll ans = 0;
		L[mm + 1] = 0,R[mm + 1] = n;
		for(int i = mm;i >= 1;--i)
			get(Lc[t[i]],Rc[t[i]],L[i + 1],R[i + 1],L[i],R[i],1);
		int tl = 1,tr = n;
		for(int i = 1;i <= mm;++i) {
			for(int j = 'a';j <= 'z';++j) {
				if(j == t[i]) continue;
				int x,y;
				get(tl,tr,Lc[j],Rc[j],x,y,i - 1);

				get(x,y,L[i + 1],R[i + 1],x,y,i);

				ans += max(0,y - x + 1);
			}
			get(tl,tr,Lc[t[i]],Rc[t[i]],tl,tr,i - 1);
		}
		cout<<ans<<endl;
	}
	return 0;
}

B.A

problem

在n天完成m项工作,每天可以完成无数项,在第j天完成第i项工作的收益是\(w[i][j]\)\(w[i][j]=-1\)时,表示不能再第j天完成第j项工作。还有K个限制,每个限制给出\(x,y\),需要满足x这项工作在y之前完成。问最大收益。

solution

比较经典的最小割问题。

但是这道题要求的是最大收益,所以把流量取负,然后加上一个定值,最后在减去就行了。

对于每项工作建m条边,对于第x个工作,第i个点向第i+1个点连流量为\(w[x][i]\)的边,割掉这条边表示在第i天完成工作。然后对于每个限制\(x,y\),从x的第i个点向j的第\(i+1\)个点连流量为INF的边。

code


/*
* @Author: wxyww
* @Date:   2020-06-03 09:34:34
* @Last Modified time: 2020-06-03 10:10:55
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 1000010,INF = 1e9;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
struct node {
	int v,nxt,w;
}e[N << 1];
int head[N],ejs = 1;

void add(int u,int v,int w) {
	// printf("!!%d %d %d\n",u,v,w);
	e[++ejs].v = v;e[ejs].nxt = head[u];head[u] = ejs;e[ejs].w = w;
	e[++ejs].v = u;e[ejs].nxt = head[v];head[v] = ejs;e[ejs].w = 0;
}

int w[110][110],n,m,K,S,T;
int p(int x,int y) {
	return (x - 1) * (m + 1) + y;
}
int dep[N];
queue<int>q;
int bfs() {
	memset(dep,0,sizeof(dep));
	dep[S] = 1;q.push(S);
	// cout<<T<<endl;
	while(!q.empty()) {
		int u = q.front();q.pop();
		for(int i = head[u];i;i = e[i].nxt) {
			int v = e[i].v;
			if(!dep[v] && e[i].w) {
				// if(v == 8) printf("%d\n",u);
				dep[v] = dep[u] + 1;q.push(v);
			}
		}
	}
	return dep[T];
}
int cur[N];
int dfs(int u,int now) {
	if(u == T) return now;
	int ret = 0;
	for(int &i = cur[u];i;i = e[i].nxt) {
		int v = e[i].v;
		if(dep[v] == dep[u] + 1 && e[i].w) {
			int k = dfs(v,min(e[i].w,now - ret));
			e[i].w -= k;e[i ^ 1].w += k;
			ret += k;
			if(ret == now) return ret;
		}
	}
	return ret;
}
int dinic() {
	int ret = 0;
	while(bfs()) {
		
		// puts("!!!");

		memcpy(cur,head,sizeof(head));
		ret += dfs(S,INF);
		// cout<<ret<<endl;
	}
	return ret;
}
int main() {
	n = read(),m = read(),K = read();
	S = n * (m + 1) + 1,T = S + 1;
	for(int i = 1;i <= n;++i) {
		for(int j = 1;j <= m;++j) {
			int x = read();
			add(p(i,j),p(i,j + 1),x == -1 ? INF : 100 - x);
		}
	}

	for(int i = 1;i <= n;++i) {
		add(S,p(i,1),INF);
		add(p(i,m + 1),T,INF);
	}

	for(int i = 1;i <= K;++i) {
		int x = read(),y = read();
		for(int j = 1;j <= m;++j)
			add(p(x,j),p(y,j + 1),INF);
	}

	cout<<100 * n - dinic();

	return 0;
}

C.逆序对

problem

给出一个残缺的长度为n的序列,每个位置的数字大小都在\([1,K]\),给残缺的位置填上\([1,k]\)之间的数,使得逆序对数量最大。

\(n\le 2\times 10^5,K\le 100\)

solution

最后脑抽写了个\(n^2\)求原数组逆序对+没开longlong。成功\(100\rightarrow 10\)。。。

首先填的数字肯定是单调不升的。如果有两个位置填的分别是x,y。满足\(x<y\),交换x和y的位置答案肯定不会变劣。

先预处理出\(w[i][j]\)表示前i个残缺的位置全都填j的时候原数组对其产生的逆序对数量。

\(f[i][j]\)表示第i个残缺的位置填的数大于等于j的最大收益。然后我们枚举一下上次填的与这次不同的数的位置k,也就是说区间\([k,j]\)填的全都是j。然后计算产生的逆序对数量,首先肯定要先加上k前面填比j大的数字产生的最大贡献有\(f[k][j+1]\),然后计算一下原数组对\([k,i]\)这段区间填的j产生的贡献,就是\(w[i][j]-w[k][j]\)。然后因为后面填的数字肯定都要比j小,所以还要再加上后面填的数字对\([k,i]\)产生的贡献\((i-k)*(m-i)\)(m是残缺位置的总数量)。

综上,转移方程就是\(f[i][j]=\max\limits_{k=0}^{i-1}f[k][j+1]+w[i][j]-w[k][j]+(i-k)*(m-i)\)

直接转移的复杂度是\(O(n^2 K)\),考虑优化。

将上面的转移方程拆开

\(f[i][j]=\max\limits_{k=0}^{i-1}w[i][j]+im-i^2+f[k][j+1]-w[k][j]-km-ik\)

前面这块\(w[i][j]+im-i^2\)是定值,后面的\(f[k][j+1]-w[k][j]-km\)\(b\)\(k\)是斜率,\(i\)是x。所以就是\(kx+b\)的形式,\(k\)\(i\)单调递增,所以用斜率优化将复杂度优化成\(O(nk)\)就行了

code

/*
* @Author: wxyww
* @Date:   2020-06-03 09:05:56
* @Last Modified time: 2020-06-03 19:24:45
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 200004;
#define int ll
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
ll w[N][104],f[2][N];
int treel[N],treer[N],n,K,b[N],a[N],m;
void updater(int x,int c) {
	while(x <= K) treer[x] += c,++x;
}
void updatel(int x,int c) {
	while(x >= 1) treel[x] += c,--x;
}
int q[2][N],H[2],T[2];
ll getb(int k,int j) {
	return f[j & 1][k] - w[k][j - 1] - m * k;
}
ll calc(int k,int i,int j) {
	ll b = getb(k,j);
	return b + i * k;
}
ll check(int k1,int k2,int k3,int j) {
	int b1 = getb(k1,j),b2 = getb(k2,j),b3 = getb(k3,j);
	return (b3 - b1) * (k1 - k2) < (b2 - b1) * (k1 - k3);
}
signed main() {
	n = read(),K = read();
	ll ans = 0,ret = 0;
	for(int i = 1;i <= n;++i) {
		a[i] = read();
		if(!a[i]) b[++m] = i;
		if(a[i]) {
			updater(a[i],1);
		}
	}
	int p = 1;
	for(int i = 1;i <= m;++i) {
		while(p < b[i]) {
			if(!a[p]) {++p;continue;}
			ret += treel[a[p] + 1];
			updater(a[p],-1);
			updatel(a[p],1);
			++p;
		}
		for(int j = 1;j <= K;++j) {
			w[i][j] = w[i - 1][j] + treer[j - 1] + treel[j + 1];
		}
	}

	while(p <= n) {
		if(!a[p]) {++p;continue;}
		ret += treel[a[p] + 1];
		updatel(a[p],1);
		++p;
	}


	for(int j = K;j >= 1;--j) {
		
		int t = j & 1,t_1 = t ^ 1;
		H[t_1] = 0;T[t_1] = 0;
		
		for(int i = 1;i <= m;++i) {
			f[t][i] = f[t_1][i];
			while(H[t_1] < T[t_1] && calc(q[t_1][H[t_1]],i,j + 1) < calc(q[t_1][H[t_1] + 1],i,j + 1)) {
				++H[t_1];
			}
			
			f[t][i] = max(f[t][i],w[i][j] - i * i + m * i + calc(q[t_1][H[t_1]],i,j + 1));
		
			while(H[t_1] < T[t_1] && check(q[t_1][T[t_1] - 1],q[t_1][T[t_1]],i,j + 1)) --T[t_1];
			
			q[t_1][++T[t_1]] = i;
		}

		ans = max(ans,f[t][m]);
	}
	cout<<ans + ret<<endl;
	return 0;
}
posted @ 2020-06-04 07:48  wxyww  阅读(41)  评论(0编辑  收藏  举报