• 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();
    }
    
  • 相关阅读:
    OEA框架学习:缓存
    2012年 博文整理
    技术支持经验总结
    OEA框架学习:元数据设计
    安装后新建Android出现“AndroidManifest.xml 系统找不到指定的文件”解决方案
    Android控件学习笔记之 GridView(实现九宫格)
    获取url地址中主机的域名
    C# 语音读取字符串
    JSON省市联动
    MOTO Droid手机自定义本地铃声设置方法
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15202140.html
Copyright © 2020-2023  润新知