Codeforces Round #741 (Div. 2)

D. Two Hundred Twenty One

题目描述

给定长度为 (n) 的序列 (a),其中 (a_i={-1,1}),定义一个序列的权值为:

[sum_{i=1}^n(-1)^{i-1}a_i ]

(q) 组询问,每组询问把区间 ([l,r]) 当成序列,问至少删除多少个数才能使序列权值为零,并给出方案。

(n,qleq 3cdot 10^5)

解法

比赛时候猜了个结论:答案不超过 (2);然后用 ( t STL) 暴力操过了 ( t easy version)

其实这个结论还可以强化,我们考虑如果这个序列直接合法就输出 (0);如果序列长度是奇数的话必须操作奇数次,并且操作次数至少是 (1);如果序列长度是偶数的话必须操作偶数次,并且操作次数至少是 (2)

那么我们可以转证结论:任何长度为 (1) 的序列都可以通过一步删除合法

(b_i) 为序列删除第 (i) 个元素后所得到的序列权值,设 (f(l,r)) 为区间 ([l,r]) 的权值,先看两个性质。

性质一:(|b_{i}-b_{i+1}|=0/2)

如果 (a_i=a_{i+1}) 那么 (b_i=b_{i+1});否则 (b_i=f(1,i-1)pm a_{i+1}mp f(i+2,n))(b_{i+1}=f(1,i)mp f(i+2,n)),那么 (|b_i-b_{i+1}|=2)

性质二:(b_1cdot b_nleq 0)

因为 (b_1=-f(1,n)pm 1,b_n=f(1,n)pm 1),显然它们要么异号要么有人等于零。

(b) 看成一个连续函数,因为两端点异号(非严格),所以一定有 (b_k=0) 在某一个 (k) 处取得。

那么算法流程是,对于奇数序列找到这个零点 (k),对于偶数序列把 (l) 删掉之后转成奇数序列即可。

总结

对于求函数等于某值怎么取到的题,可以先证函数的连续性,再证端点的分布即可。

( t Freopen) 有言:猜了结论要继续深化,可能深化到了某一步之后才利于证明的进行。

#include <cstdio>
#include <iostream>
#include <set>
#include <map>
using namespace std;
const int M = 300005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,q,f[M];char a[M];
map<int,int> mp;set<int> s[M];
int sig(int x) {return x%2?-1:1;}
void work()
{
    n=read();q=read();m=0;
    mp.clear();
    for(int i=1;i<=n;i++) s[i].clear();
    scanf("%s",a+1);f[n+1]=0;
    for(int i=n;i>=1;i--)
    {
        if(a[i]=='+') f[i]=1;
        else f[i]=-1;
        f[i]=f[i]-f[i+1];
        int x=sig(i)*(f[i]-f[i+1]);
        if(!mp[x]) mp[x]=++m;
        s[mp[x]].insert(i);
    }
    while(q--)
    {
        int l=read(),r=read();
        if(f[l]==f[r+1]*sig(r-l+1))
        {
            puts("0");
            continue;
        }
        int L=(r-l)%2==0?l:l+1;
        int x=f[L]*sig(L)-f[r+1]*sig(r),y=mp[x];
        set<int>::iterator it=s[y].lower_bound(l);
    	if(L==l+1)
    		printf("2
%d %d
",l,*it);
		else
			printf("1
%d
",*it);
    }
}
signed main()
{
    T=read();
    while(T--) work();
}

E. Rescue Niwen!

题目描述

点此看题

给定一个长度为 (n) 的字符串,把所有子串按照 [1,1],[1,2]...[1,n],[2,2]...[2,n]...[n,n] 的方式排成序列,定义小于号为字典序的小于,求出这个序列的最长上升子序列。

(nleq 5000)

Morning desert sun horizon 骄阳破晓

Rise above the sands of time... 照耀时之沙漠

解法

本题只能用 (O(n^2)) 做法,比赛时猜个结论直接过了~

暴力思路就是直接考虑每个子串结尾的最长上升子序列,首先有一个 ( t naive)( t observation):答案只可能在 ([1,n],[2,n],[3,n]...) 这些地方取得,因为 (s[i,j]<s[i,j+1]),不在这里取得就还可以增加。

那么尝试只计算以这些点结尾的 (dp) 值,对后缀 (i) 转移的时候我们枚举后缀 (j),找到它们的最长公共前缀 (x),如果 (s[j+x]<s[i+x]) 那么把后缀 (i) 的一段后缀接到最长上升子序列后面即可。

上面做法的正确性基于结论:后缀 (i) 的有效转移是所有 (j<i) 的后缀 (j)

(x) 为后缀 (i) 中第一个满足 (s[j,k]<s[i,x]) 的位置 (x),分这三种情况讨论:

  • 如果同时满足 (s[j,k+1]<s[i,x]),那么显然从 (s[j,k+1]) 转移更优。
  • 否则满足 (s[j,k+1]<s[i,x+1]),那么从两个点转移都一样优。
  • 如果到 (k+1) 这里就已经找不到 (s[j,k+1]<s[i,y]),那么从两个点转移都不优。

那么显然靠后的点转移就优,所以只需要考虑后缀的转移。

写一个后缀数组求 ( t lcp),时间复杂度 (O(n^2))

总结

考虑有效状态又多了一种思路:只求出跟答案相关的 (dp) 值即可,只用于转移的 (dp) 值可以考虑其性质。

推转移点的性质时,这样的思路也是成立的:某种情况某某优;某种情况一样优;某某情况都不优。这个很像凸包,足以说明某个转移点一定不优。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 5005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,lg[M],dp[M],ans;char s[M];
//init for suffix array
int x[M],y[M],c[M],sa[M],rk[M],hi[M][20];
void init()
{
	int m=200;
	memset(x,0,sizeof x);
	memset(y,0,sizeof y);
	memset(c,0,sizeof c);
	memset(sa,0,sizeof sa);
	memset(rk,0,sizeof rk);
	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=1;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];//mistake
		swap(x,y);
		x[sa[1]]=num=1;
		for(int i=2;i<=n;i++)
			x[sa[i]]=(y[sa[i]]==y[sa[i-1]]
			&& y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
		if(m==n) break;
		m=num;
	}
	int k=0;
	for(int i=1;i<=n;i++) rk[sa[i]]=i;
	for(int i=1;i<=n;i++)
	{
		if(rk[i]==1) continue;
		if(k) k--;
		int j=sa[rk[i]-1];
		while(i+k<=n && j+k<=n && s[i+k]==s[j+k]) k++;
		hi[rk[i]][0]=k;
	}
    for(int i=2;i<=n;i++)
        lg[i]=lg[i>>1]+1;
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			hi[i][j]=min(hi[i][j-1],hi[i+(1<<j-1)][j-1]);
}
int lcp(int l,int r)
{
	l=rk[l];r=rk[r];
	if(l>r) swap(l,r);l++;
	int k=lg[r-l+1];
	return min(hi[l][k],hi[r-(1<<k)+1][k]);
}
//my code
void work()
{
    n=read();scanf("%s",s+1);
    init();ans=0;
    for(int i=1;i<=n;i++)
    {
        dp[i]=n-i+1;
        for(int j=1;j<i;j++)
        {
            int x=lcp(i,j);
            if(s[j+x]<s[i+x])
                dp[i]=max(dp[i],dp[j]+n-i+1-x);
        }
        ans=max(ans,dp[i]);
    }
    printf("%d
",ans);
}
signed main()
{
    T=read();
    while(T--) work();
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15202140.html