zoukankan      html  css  js  c++  java
  • #194 sequence(搜索+动态规划+主席树)

      考虑按顺序暴搜子序列。如果序列中的数两两不同,显然每次给上一个找到的子序列添上后缀最小值,即为下一个要找的子序列。如果不能再加了就回溯继续考虑后缀次小、第三小……值,直到找到k个子序列。

      有重复的数后,考虑后缀k小值只取第一次出现的位置,并在每找到一个子序列后就统计其出现次数。显然这样就能找到所有要找的子序列,因为序列末端选择位置更靠前,后面的选择更多。

      求一个序列在另一个序列里的出现次数显然可以dp,即设f[i][j]为第一个序列的i位置和第二个序列的j位置匹配的方案数。当然答案序列可能很长,dp数组可能开不下,不过注意到有ai<=30的部分分,可以猜想这个部分的答案序列长度不会很长,先考虑拿部分分。

      每找到一个序列就暴力dp即为O(n2k)。注意到dp时可以直接继承上层dfs的dp数组,于是复杂度O(nk)。这里的k一般来说并不能跑满,实际上是本质不同的答案中出现的子序列个数。但是很容易卡满,比如放99970个30,最后将1到30倒序加入序列,这样每个答案序列都是不同的,就被卡成暴力了。

      但上面这个hack数据有比较特殊的地方,即存在于答案中的数字基本上出现次数很少。注意到我们之前的dp实际上可以将做一次的复杂度优化到序列最后一个数的出现次数(*log)。并且dp时可以直接从搜到的位置开始。这样上面的数据就hack不掉了。同时如果要接着hack这个做法,使dp部分运算次数增加,本质不同的答案子序列数量又会减少。这样这个做法就根本卡不掉并且跑得飞快了,复杂度O(玄学)。

      但实际上可以冷静分析一下复杂度,如果某次dp时该数dp值不为0的位置个数为x(显然因为是从搜到的位置开始的,dp值均>0),我们至少就找到了x个答案中的子序列。所以这一部分复杂度其实是O(k)(*log)的。大概就是所谓卡常卡着卡着发现复杂度对了?

      还剩下一点问题,就是上面的dp数组开不下,以及要找后缀k小值。第一个问题直接对dp数组开vector,显然空间是线性的。第二个是主席树板子题。总复杂度O(klogn)。

    #include<iostream> 
    #include<cstdio>
    #include<cmath>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    using namespace std;
    #define ll long long
    #define N 100010
    char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
    int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
    int read()
    {
    	int 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<<1)+(x<<3)+(c^48),c=getchar();
    	return x*f;
    }
    int n,m,V,a[N],root[N],nxt[N],p[N],seed,P,tot,cnt;
    vector<int> pos[N],f[N];
    struct data{int l,r,x,pos;
    }tree[N<<6];
    void ins(int &k,int l,int r,int x,int p)
    {
    	tree[++cnt]=tree[k];k=cnt;
    	if (l==r) {tree[k].x=1,tree[k].pos=p;return;}
    	int mid=l+r>>1;
    	if (x<=mid) ins(tree[k].l,l,mid,x,p);
    	else ins(tree[k].r,mid+1,r,x,p);
    	tree[k].x=tree[tree[k].l].x+tree[tree[k].r].x;
    }
    int query(int k,int l,int r,int x)
    {
    	if (l==r) return tree[k].pos;
    	int mid=l+r>>1;
    	if (x<=tree[tree[k].l].x) return query(tree[k].l,l,mid,x);
    	else return query(tree[k].r,mid+1,r,x-tree[tree[k].l].x);
    }
    int findnxt(int k,int x){return query(root[k+1],0,V,x);}
    int findpre(int x,int k){return lower_bound(pos[x].begin(),pos[x].end(),k)-pos[x].begin()-1;}
    void dfs(int k,int cur,int h)
    {
    	int last=0;
    	while (tot<m&&k<n)
    	{
    		last++;int u=findnxt(k,last);
    		if (u>n) break;
    		int H=(1ll*h*seed+a[u])%P;
    		f[cur+1].clear();pos[cur+1].clear();
    		for (int i=u,cnt=0;i<=n;i=nxt[i],cnt++)
    		{
    			pos[cur+1].push_back(i);
    			if (i!=u) f[cur+1].push_back(f[cur+1][cnt-1]);
    			else f[cur+1].push_back(0);
    			int x=findpre(cur,i);
    			if (x>=0) f[cur+1][cnt]+=f[cur][x];
    			f[cur+1][cnt]=min(f[cur+1][cnt],m-tot);
    		}
    		int v=f[cur+1][f[cur+1].size()-1];
    		for (int i=1;i<=v;i++) printf("%d
    ",H);
    		tot+=v;
    		dfs(u,cur+1,H);
    	}
    }
    int main()
    {
    	freopen("sequence.in","r",stdin);
    	freopen("sequence.out","w",stdout);
    	n=read(),m=read(),seed=read(),P=read();
    	for (int i=1;i<=n;i++) V=max(V,a[i]=read());
    	V++;a[n+1]=V;
    	root[n+2]=0;
    	for (int i=n+1;i>=1;i--)
    	{
    		root[i]=root[i+1];
    		ins(root[i],0,V,a[i],i);
    	}
    	nxt[n+1]=n+1;for (int i=0;i<=V;i++) p[i]=n+1;
    	for (int i=n;i>=0;i--) nxt[i]=p[a[i]],p[a[i]]=i;
    	f[0].push_back(1);pos[0].push_back(0);
    	dfs(0,0,0);
    	return 0;
    }
    

      

  • 相关阅读:
    机器学习笔记
    使用pelican创建静态博客
    farbox editor是个好东西
    MamBa项目的插件编写-TikiTorch生成器
    通过rundll32运行C#DLL转储内存
    通过调用Windows本地RPC服务器bypass UAC
    浅谈python反序列化漏洞
    [转载]SQL Server提权系列
    certutil在传输payload中的新奇技巧
    AVIator -- Bypass AV tool
  • 原文地址:https://www.cnblogs.com/Gloid/p/10376522.html
Copyright © 2011-2022 走看看