1
7
2016
0

【poj1743】 Musical Theme

题意:给你一个数字串,找到最长长度且不重叠的两个子串,使得这两个子串变化相同。

什么叫变化相同呢,就是比如{1,3,4,6,9}与{5,7,8,10,13}就是变化相同的。

首先我们可以把数字串的相邻两个相减,得到的新序列。我们只要在这个新序列中找到最长的相等的且不重叠的两个子串。我们可以先二分答案,然后由于lcp的性质,height可以分为若干组,每组中的height都是大于等于mid的,只要同一组存在两个数使得它们在原串的的位置差大于mid,就可行。

代码:

 

#include<cstdio>
#include<algorithm>
using namespace std;
int n,x,y;
int wa[20010],wb[20010],ws[20010],wv[20010],r[20010],sa[20010],rank[20010],height[20010];
int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
void da(int *r,int *sa,int n,int m)
{
	int i,j,p,*x=wa,*y=wb,*t;
	for (i=0;i<m;i++) ws[i]=0;
	for (i=0;i<n;i++) ws[x[i]=r[i]]++;
	for (i=1;i<m;i++) ws[i]+=ws[i-1];
	for (i=n-1;i>=0;i--) sa[--ws[x[i]]]=i;
	for (j=1,p=1;p<n;j*=2,m=p)
	{
		p=0;
		for (i=n-j;i<n;i++) y[p++]=i;
		for (i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;
		for (i=0;i<n;i++) wv[i]=x[y[i]];
		for (i=0;i<m;i++) ws[i]=0;
		for (i=0;i<n;i++) ws[wv[i]]++;
		for (i=1;i<m;i++) ws[i]+=ws[i-1];
		for (i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
		for (t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
			x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
	}
}
void calheight(int *r,int *sa,int n)
{
	int i,j,k=0;
	for (i=1;i<=n;i++) rank[sa[i]]=i;
	for (i=0;i<n;height[rank[i++]]=k)
		for (k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
int check(int *sa,int n,int mid)
{
	int Min=sa[1],Max=sa[1];
	for (int i=2;i<=n;i++)
	{
		if (height[i]<mid) Min=Max=sa[i];else
		{
			Max=max(Max,sa[i]);
			Min=min(Min,sa[i]);
			if (Max-Min>mid) return 1;
		}
	}
	return 0;
}
int main()
{
	while (1)
	{
		scanf("%d",&n);
		if (!n) break;
		scanf("%d",&y);
		n--;
		for (int i=0;i<n;i++)
		{
			scanf("%d",&x);
			r[i]=x-y+100;
			y=x;
		}
		r[n]=0;
		da(r,sa,n+1,200);
		calheight(r,sa,n);
		int l=1,r=n/2;
		while (l<r)
		{
			int mid=((l+r)>>1)+1;
			if (check(sa,n,mid)) l=mid;else r=mid-1;
		}
		if (r>=4) printf("%d\n",r+1);else printf("0\n");
	}
}
Category: 后缀数组 | Tags:
1
7
2016
0

【poj2774】Long Long Message

本题是翻开后缀数组专题的第一题。

题意:给你两个字符串A,B,求两个字符串的最长公共子串。

求两个字符串的最长公共子串也就是求两个字符串的两个后缀的最长公共前缀的最大值。

那么我们只要将B复制要A后面,中间加个分隔符。求出sa,rank,height三个数组。之后,我们只要求sa[i-1],sa[i]在不同字符串中且height[i]最大的height值。

代码:

 

#include<cstdio>
#include<cstring>
using namespace std;
int l,n,ans;
char s[200010];
int sa[200010],r[200010],rank[200010],height[200010],wa[200010],wb[200010],ws[200010],wv[200010];
int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
void da(int *r,int *sa,int n,int m)
{
	int i,j,p,*x=wa,*y=wb,*t;
	for (i=0;i<m;i++) ws[i]=0;
	for (i=0;i<n;i++) ws[x[i]=r[i]]++;
	for (i=1;i<m;i++) ws[i]+=ws[i-1];
	for (i=n-1;i>=0;i--) sa[--ws[x[i]]]=i;
	for (j=1,p=1;p<n;j*=2,m=p)
	{
		for (p=0,i=n-j;i<n;i++) y[p++]=i;
		for (i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;
		for (i=0;i<n;i++) wv[i]=x[y[i]];
		for (i=0;i<m;i++) ws[i]=0;
		for (i=0;i<n;i++) ws[wv[i]]++;
		for (i=1;i<m;i++) ws[i]+=ws[i-1];
		for (i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
		for (t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
			x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
	}
}
void calheight(int *r,int *sa,int n)
{
	int i,j,k=0;
	for (i=1;i<=n;i++) rank[sa[i]]=i;
	for (i=0;i<n;height[rank[i++]]=k)
	for (k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
int main()
{
	scanf("%s",s);
	l=strlen(s);
	s[l]=1;
	scanf("%s",s+l+1);
	n=strlen(s);
	for (int i=0;i<n;i++) r[i]=s[i];
	r[n]=0;
	da(r,sa,n+1,128);
	calheight(r,sa,n);
	for (int i=2;i<=n;i++)
	if (height[i]>ans)
	if ((l<sa[i-1]&&l>sa[i])||(l>sa[i-1]&&l<sa[i])) ans=height[i];
	printf("%d\n",ans);
}
Category: 后缀数组 | Tags:
1
4
2016
0

【bzoj4327】【JSOI2012】玄武密码

4327: JSOI2012 玄武密码

Time Limit: 10 Sec  Memory Limit: 512 MB
Submit: 43  Solved: 20
[Submit][Status][Discuss]

Description

在美丽的玄武湖畔,鸡鸣寺边,鸡笼山前,有一块富饶而秀美的土地,人们唤作进香河。相传一日,一缕紫气从天而至,只一瞬间便消失在了进香河中。老人们说,这是玄武神灵将天书藏匿在此。 
很多年后,人们终于在进香河地区发现了带有玄武密码的文字。更加神奇的是,这份带有玄武密码的文字,与玄武湖南岸台城的结构有微妙的关联。于是,漫长的破译工作开始了。 
经过分析,我们可以用东南西北四个方向来描述台城城砖的摆放,不妨用一个长度为N的序列来描述,序列中的元素分别是‘E’,‘S’,‘W’,‘N’,代表了东南西北四向,我们称之为母串。而神秘的玄武密码是由四象的图案描述而成的M段文字。这里的四象,分别是东之青龙,西之白虎,南之朱雀,北之玄武,对东南西北四向相对应。 
现在,考古工作者遇到了一个难题。对于每一段文字,其前缀在母串上的最大匹配长度是多少呢? 

Input

第一行有两个整数,N和M,分别表示母串的长度和文字段的个数。 
第二行是一个长度为N的字符串,所有字符都满足是E,S,W和N中的一个。 
之后M行,每行有一个字符串,描述了一段带有玄武密码的文字。依然满足,所有字符都满足是E,S,W和N中的一个。 

Output

输出有M行,对应M段文字。 
每一行输出一个数,表示这一段文字的前缀与母串的最大匹配串长度。 

Sample Input

7 3
SNNSSNS
NNSS
NNN
WSEE

Sample Output

4
2
0

HINT

对于100%的数据,N<=10^7,M<=10^5,每一段文字的长度<=100。

用AC自动机。

首先,我们把所有的匹配串放进自动机,我们可以计算出每个点的字符串是否出现在模式串中。这只需要把模式串在自动机上走一趟,每走到一个点就一直访问后缀节点直到到达了根节点,访问过的点都标记一下。然后每个点只会被访问一次。这样我们就处理完了所有点是否出现在模式串中。对于每个字符串计算答案时,从最后一个点访问起,每次走到它的父亲,一旦走到的点被标记过,就输出当前的长度,退出。

我也感觉非常精妙。。

代码:

 

#include<cstdio>
#include<cstring>
using namespace std;
int n,m,len,now,num,p1,p2,u,v;
int q[10000010],hz[10000010],fa[10000010],id[10000010],l[10000010],flag[10000010],trie[10000010][4];
char str[10000010],s[110];
int ord(char ch){if (ch=='E') return 0;if (ch=='S') return 1;if (ch=='W') return 2;return 3;}
void bfs()
{
    p2=1;
    while (p1<p2)
    {
        p1++;
        u=q[p1];
        for (int i=0;i<4;i++)
        if (trie[u][i])
        {
            v=trie[u][i];
            if (!u) hz[v]=0;else hz[v]=trie[hz[u]][i];
            q[++p2]=v;
        }else trie[u][i]=trie[hz[u]][i];
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    scanf("%s",str);
    for (int i=1;i<=m;i++)
    {
        scanf("%s",s);
        len=strlen(s);
        now=0;
        for (int j=0;j<len;j++)
        if (trie[now][ord(s[j])]) now=trie[now][ord(s[j])];else
        {
            num++;
            trie[now][ord(s[j])]=num;
            fa[num]=now;
            now=num;
        }
        id[i]=now;l[i]=len;
    }
    bfs();
    u=0;
    for (int i=0;i<n;i++)
    {
        u=trie[u][ord(str[i])];
        for (v=u;v;v=hz[v]) if (flag[v]) break;else flag[v]=1;
    }
    for (int i=1;i<=m;i++)
    {
        u=id[i];
        for (;l[i];l[i]--,u=fa[u]) if (flag[u])break;
        printf("%d\n",l[i]);
    }
}

Category: AC自动机 | Tags:
12
28
2015
0

【bzoj3343】教主的魔法

3343: 教主的魔法

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 696  Solved: 300
[Submit][Status][Discuss]

Description

教主最近学会了一种神奇的魔法,能够使人长高。于是他准备演示给XMYZ信息组每个英雄看。于是N个英雄们又一次聚集在了一起,这次他们排成了一列,被编号为1、2、……、N
每个人的身高一开始都是不超过1000的正整数。教主的魔法每次可以把闭区间[LR](1≤LRN)内的英雄的身高全部加上一个整数W。(虽然L=R时并不符合区间的书写规范,但我们可以认为是单独增加第LR)个英雄的身高)
CYZ、光哥和ZJQ等人不信教主的邪,于是他们有时候会问WD闭区间 [LR] 内有多少英雄身高大于等于C,以验证教主的魔法是否真的有效。
WD巨懒,于是他把这个回答的任务交给了你。
 

Input

       第1行为两个整数NQQ为问题数与教主的施法数总和。
       第2行有N个正整数,第i个数代表第i个英雄的身高。
       第3到第Q+2行每行有一个操作:
(1)       若第一个字母为“M”,则紧接着有三个数字LRW。表示对闭区间 [LR] 内所有英雄的身高加上W
(2)       若第一个字母为“A”,则紧接着有三个数字LRC。询问闭区间 [LR] 内有多少英雄的身高大于等于C
 

Output

       对每个“A”询问输出一行,仅含一个整数,表示闭区间 [LR] 内身高大于等于C的英雄数。
 

Sample Input

5 3
1 2 3 4 5
A 1 5 4
M 3 5 1
A 1 5 4

Sample Output

2
3

HINT

【输入输出样例说明】

原先5个英雄身高为1、2、3、4、5,此时[1, 5]间有2个英雄的身高大于等于4。教主施法后变为1、2、4、5、6,此时[1, 5]间有3个英雄的身高大于等于4。

 

【数据范围】

对30%的数据,N≤1000,Q≤1000。
对100%的数据,N≤1000000,Q≤3000,1≤W≤1000,1≤C≤1,000,000,000。
 

刷道水。。

直接分块就好了。

代码:

 

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,m,len,num,ans,x,y,l,r;
int L[1010],R[1010];
ll tag[1010],val;
char ch[5];
struct node{ll x;int loc;}a[1000010];
bool cmp(const node x,const node y){return x.x<y.x;}
int ask(int k,int l,int r,ll x)
{
    int ans=0;
    for (int i=L[k];i<=R[k];i++)
    if (a[i].loc>=l&&a[i].loc<=r&&a[i].x>=x) ans++;
    return ans; 
}
void add(int k,int l,int r,ll x)
{
    for (int i=L[k];i<=R[k];i++)
    if (a[i].loc>=l&&a[i].loc<=r) a[i].x+=x;
}
int binary(int l,int r,ll x)
{
    if (l==r){if (a[l].x<x) return l+1;else return l;}
    int mid=(l+r)>>1;
    if (a[mid].x>=x) return binary(l,mid,x);else return binary(mid+1,r,x);
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++){scanf("%lld",&a[i].x);a[i].loc=i;}
    len=(int)sqrt(n);
    if (n%len==0) num=n/len;else num=n/len+1;
    for (int i=1;i<=num;i++)
    {
        L[i]=(i-1)*len+1;
        R[i]=min(n,i*len);
        sort(a+L[i],a+R[i]+1,cmp);
    }
    while (m--)
    {
        scanf("%s",ch);
        if (ch[0]=='A')
        {
            scanf("%d%d%lld",&x,&y,&val);
            l=x/len;if (R[l]<x) l++;
            r=y/len;if (R[r]<y) r++;
            ans=0;
            if (l==r){ans+=ask(l,x,y,val);printf("%d\n",ans);continue;}
            ans+=ask(l,x,R[l],val);
            ans+=ask(r,L[r],y,val);
            for (int i=l+1;i<r;i++)
                ans+=R[i]-binary(L[i],R[i],val-tag[i])+1;
            printf("%d\n",ans);
        }else
        {
            scanf("%d%d%lld",&x,&y,&val);
            l=x/len;if (R[l]<x) l++;
            r=y/len;if (R[r]<y) r++;
            if (l==r){add(l,x,y,val);continue;}
            add(l,x,R[l],val);
            add(r,L[r],y,val);
            for (int i=l+1;i<r;i++)
                tag[i]+=val;
        }
    }
}

Category: 分块 | Tags:
12
27
2015
0

【bzoj2243】【SDOI2011】染色

2243: [SDOI2011]染色

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 4270  Solved: 1608
[Submit][Status][Discuss]

Description

 

给定一棵有n个节点的无根树和m个操作,操作有2类:

1、将节点a到节点b路径上所有点都染成颜色c

2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“1122213段组成:“11、“222和“1

请你写一个程序依次完成这m个操作。

 

Input

第一行包含2个整数nm,分别表示节点数和操作数;

第二行包含n个正整数表示n个节点的初始颜色

下面 行每行包含两个整数xy,表示xy之间有一条无向边。

下面 行每行描述一个操作:

“C a b c”表示这是一个染色操作,把节点a到节点b路径上所有点(包括ab)都染成颜色c

“Q a b”表示这是一个询问操作,询问节点a到节点b(包括ab)路径上的颜色段数量。

 

Output

对于每个询问操作,输出一行答案。

 

Sample Input

6 5
 
2 2 1 2 1 1
 
1 2
 
1 3
 
2 4
 
2 5
 
2 6
 
Q 3 5
 
C 2 1 1
 
Q 3 5
 
C 5 1 2
 
Q 3 5
 

Sample Output

3
 
1
 
2

HINT

N<=10^5,操作数M<=10^5,所有的颜色C为整数且在[0, 10^9]之间。

Source

树链剖分,注意一下询问的细节就可以了。

代码:

 

#include<cstdio>
#include<algorithm>
#define maxn 1000000
using namespace std;
int n,m,u,v,tot,opt,a,b,c,edgenum;
int f[maxn],d[maxn],num[maxn],son[maxn],tid[maxn],pre[maxn],top[maxn],col[maxn],vet[maxn],next[maxn],head[maxn];
char s[5];
struct node{int l,r,sum,tag;}tr[maxn];
void add(int u,int v)
{
    edgenum++;
    vet[edgenum]=v;
    next[edgenum]=head[u];
    head[u]=edgenum;
}
void dfs(int u,int dep,int fa)
{
    f[u]=fa;
    d[u]=dep;
    num[u]=1;
    int maxnum=0;
    for (int e=head[u];e;e=next[e])
    {
        int v=vet[e];
        if (v!=fa)
        {
            dfs(v,dep+1,u);
            num[u]+=num[v];
            if (num[v]>maxnum){maxnum=num[v];son[u]=v;}
        }
    }
}
void dfs(int u,int number)
{
    top[u]=number;
    tid[u]=++tot;
    pre[tot]=u;
    if (!son[u]) return;
    dfs(son[u],number);
    for (int e=head[u];e;e=next[e])
    {
        int v=vet[e];
         
        if (v!=f[u]&&v!=son[u]) dfs(v,v);
    }
}
void build(int l,int r,int p)
{
    if (l==r){tr[p].l=tr[p].r=col[pre[l]];tr[p].sum=1;tr[p].tag=-1;return;}
    int mid=(l+r)>>1;
    build(l,mid,p*2);build(mid+1,r,p*2+1);
    tr[p].l=tr[p*2].l;
    tr[p].r=tr[p*2+1].r;
    tr[p].tag=-1;
    tr[p].sum=tr[p*2].sum+tr[p*2+1].sum-(tr[p*2].r==tr[p*2+1].l);
}
void change(int l,int r,int x,int y,int p,int c)
{
    if (l==x&&r==y){tr[p].l=tr[p].r=c;tr[p].sum=1;tr[p].tag=c;return;}
    if (tr[p].tag!=-1)
    {
        tr[p*2].tag=tr[p].tag;
        tr[p*2].l=tr[p*2].r=tr[p].tag;
        tr[p*2+1].tag=tr[p].tag;
        tr[p*2+1].l=tr[p*2+1].r=tr[p].tag;
        tr[p*2].sum=tr[p*2+1].sum=1;
        tr[p].tag=-1;
    }
    int mid=(l+r)>>1;
    if (y<=mid) change(l,mid,x,y,p*2,c);else
    if (x>mid) change(mid+1,r,x,y,p*2+1,c);else
    {
        change(l,mid,x,mid,p*2,c);
        change(mid+1,r,mid+1,y,p*2+1,c);
    }
    tr[p].l=tr[p*2].l;
    tr[p].r=tr[p*2+1].r;
    tr[p].sum=tr[p*2].sum+tr[p*2+1].sum-(tr[p*2].r==tr[p*2+1].l);
}
void update(int u,int v,int c)
{
    while (top[u]!=top[v])
    {
        change(1,n,tid[top[u]],tid[u],1,c);
        u=f[top[u]];
    }
    change(1,n,tid[v],tid[u],1,c);
}
int ask(int l,int r,int x,int y,int p)
{
    if (l==x&&r==y) return tr[p].sum;
    if (tr[p].tag!=-1)
    {
        tr[p*2].tag=tr[p].tag;
        tr[p*2].l=tr[p*2].r=tr[p].tag;
        tr[p*2+1].tag=tr[p].tag;
        tr[p*2+1].l=tr[p*2+1].r=tr[p].tag;
        tr[p*2].sum=tr[p*2+1].sum=1;
        tr[p].tag=-1;
    }
    int mid=(l+r)>>1;
    int ans;
    if (y<=mid) ans=ask(l,mid,x,y,p*2);else
    if (x>mid) ans=ask(mid+1,r,x,y,p*2+1);else
    {
    	int tmp=1;
    	if (tr[p*2].r!=tr[p*2+1].l) tmp=0;
    	ans=ask(l,mid,x,mid,p*2)+ask(mid+1,r,mid+1,y,p*2+1)-tmp;
    }
    tr[p].l=tr[p*2].l;
    tr[p].r=tr[p*2+1].r;
    tr[p].sum=tr[p*2].sum+tr[p*2+1].sum-(tr[p*2].r==tr[p*2+1].l);
    return ans;
}
int getc(int l,int r,int p,int x)
{
    if (l==r) return tr[p].l;
    if (tr[p].tag!=-1)
    {
        tr[p*2].tag=tr[p].tag;
        tr[p*2].l=tr[p*2].r=tr[p].tag;
        tr[p*2+1].tag=tr[p].tag;
        tr[p*2+1].l=tr[p*2+1].r=tr[p].tag;
        tr[p*2].sum=tr[p*2+1].sum=1;
        tr[p].tag=-1;
    }
    int mid=(l+r)>>1;
    int color;
    if (x<=mid) color=getc(l,mid,p*2,x);else color=getc(mid+1,r,p*2+1,x);
    tr[p].l=tr[p*2].l;
    tr[p].r=tr[p*2+1].r;
    tr[p].sum=tr[p*2].sum+tr[p*2+1].sum-(tr[p*2].r==tr[p*2+1].l);
    return color;
}
int solve(int u,int v)
{
    int ans=0;
    while (top[u]!=top[v])
    {
        ans+=ask(1,n,tid[top[u]],tid[u],1);
        if (getc(1,n,1,tid[top[u]])==getc(1,n,1,tid[f[top[u]]])) ans--;
        u=f[top[u]];
    }
    ans+=ask(1,n,tid[v],tid[u],1);
    return ans;
}
int lca(int u,int v)
{
    while (top[u]!=top[v])
    {
        if (d[top[u]]<d[top[v]])swap(u,v);
        u=f[top[u]];
    }
    if (d[u]<d[v]) return u;else return v;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        scanf("%d",&col[i]);
    for (int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    dfs(1,1,0);
    dfs(1,1);
    build(1,n,1);
    while (m--)
    {
        scanf("%s",s);
        if (s[0]=='C')
        {
            scanf("%d%d%d",&a,&b,&c);
            u=lca(a,b);
            update(a,u,c);update(b,u,c);
        }else
        {
            scanf("%d%d",&a,&b);
            u=lca(a,b);
            printf("%d\n",solve(a,u)+solve(b,u)-1);
        }
    }
}
Category: 树链剖分 | Tags:
12
27
2015
0

【bzoj3295】【Cqoi2011】动态逆序对

3295: [Cqoi2011]动态逆序对

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 2280  Solved: 725
[Submit][Status][Discuss]

Description

对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

Input

输入第一行包含两个整数nm,即初始元素的个数和删除的元素个数。以下n行每行包含一个1到n之间的正整数,即初始排列。以下m行每行一个正整数,依次为每次删除的元素。
 

Output

 
输出包含m行,依次为删除每个元素之前,逆序对的个数。

Sample Input

5 4
1
5
3
4
2
5
1
4
2

Sample Output

5
2
2
1
样例解释
(1,5,3,4,2)(1,3,4,2)(3,4,2)(3,2)(3)。

HINT

N<=100000 M<=50000

树状数组包线段树。。。

我们令删除的数为x,位置在loc。

首先容易想到的就是找到1~(loc-1)大于x的个数,和(loc+1)~n小于x的个数。这就是裸的主席树上加树状数组。但是这样内存是Nlog2N,是会炸的。于是我们换一种维护方式,我们先预处理出每个位置的front和back,front是指位置在其之前且值大于其值的个数,back是指位置在其之后且值小于其值的个数。

那么我们需要在主席树上维护就是删去的数。这样的内存是Mlog2N的,就可以解决这道题了。

代码:

 

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
int n,m,x,loc,cnt;
int L[35],R[35],front[100010],back[100010],b[100010],root[100010],pos[100010],a[100010];
ll ans;
struct node{int l,r;ll sum;}tr[8000010];
int ask(int x){int ans=0;for (;x;x-=x&-x) ans+=b[x];return ans;}
void add(int x){for (;x<=n;x+=x&-x) b[x]++;}
ll ask2(int l,int r,int x)
{
	if (l>r) return 0;
	l--;
	L[0]=R[0]=0;
	for (int i=l;i;i-=i&-i)
		L[++L[0]]=root[i];
	for (int i=r;i;i-=i&-i)
		R[++R[0]]=root[i];
	l=1;r=n;
	ll sum=0;
	while (l<r)
	{
		int mid=(l+r)>>1;
		if (x<=mid)
		{
			for (int i=1;i<=R[0];i++) sum+=tr[tr[R[i]].r].sum;
			for (int i=1;i<=L[0];i++) sum-=tr[tr[L[i]].r].sum;
			for (int i=1;i<=R[0];i++) R[i]=tr[R[i]].l;
			for (int i=1;i<=L[0];i++) L[i]=tr[L[i]].l;
			r=mid;
		}else
		{
			for (int i=1;i<=L[0];i++) L[i]=tr[L[i]].r;
			for (int i=1;i<=R[0];i++) R[i]=tr[R[i]].r;
			l=mid+1;
		}
	}
	return sum;
}
ll ask1(int l,int r,int x)
{
	if (l>r) return 0;
	l--;
	L[0]=R[0]=0;
	for (int i=l;i;i-=i&-i)
		L[++L[0]]=root[i];
	for (int i=r;i;i-=i&-i)
		R[++R[0]]=root[i];
	l=1;r=n;
	ll sum=0;
	while (l<r)
	{
		int mid=(l+r)>>1;
		if (x>mid)
		{
			for (int i=1;i<=R[0];i++) sum+=tr[tr[R[i]].l].sum;
			for (int i=1;i<=L[0];i++) sum-=tr[tr[L[i]].l].sum;
			for (int i=1;i<=R[0];i++) R[i]=tr[R[i]].r;
			for (int i=1;i<=L[0];i++) L[i]=tr[L[i]].r;
			l=mid+1;
		}else
		{
			for (int i=1;i<=L[0];i++) L[i]=tr[L[i]].l;
			for (int i=1;i<=R[0];i++) R[i]=tr[R[i]].l;
			r=mid;
		}
	}
	return sum;
}
void change(int &p,int l,int r,int x)
{
	tr[++cnt]=tr[p];p=cnt;tr[p].sum++;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (x<=mid) change(tr[p].l,l,mid,x);else change(tr[p].r,mid+1,r,x);
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		pos[a[i]]=i;
		front[i]=i-1-ask(a[i]);
		ans+=front[i];
		add(a[i]);
	}
	memset(b,0,sizeof(b));
	for (int i=n;i>=1;i--)
	{
		back[i]=ask(a[i]);
		add(a[i]);
	}
	while (m--)
	{
		printf("%lld\n",ans);
		scanf("%d",&x);
		loc=pos[x];
		ans-=front[loc]-ask2(1,loc-1,x);
		ans-=back[loc]-ask1(loc+1,n,x);
		for (;loc<=n;loc+=loc&-loc) change(root[loc],1,n,x);
	}
}
Category: 主席树 | Tags:
12
22
2015
0

【JSOI2015】最佳南瓜

易知,每次操作后中位数要么不变要么增加一。因此我们只要分块判断此时的中位数是否需要加一。

代码:

 

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m,mid,x,y,L,R,len,num,sum,k;
int l[10010],r[10010],b[100010],tag[10010];
struct node{int x,id;}a[100010];
bool cmp(const node x,const node y){return x.x<y.x;}
void add(int x,int L,int R)
{
    for (int i=l[x];i<=r[x];i++)if (a[i].id>=L&&a[i].id<=R)a[i].x++;
    sort(a+l[x],a+r[x]+1,cmp);
}
int binary(int l,int r,int x)
{
    if (l==r) return l;
    int mid=(l+r)>>1;
    if (a[mid].x>=x) return binary(l,mid,x);return binary(mid+1,r,x);
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i].x);
        a[i].id=i;
    }
    for (int i=1;i<=n;i++)
        b[i]=a[i].x;
    nth_element(b+1,b+(n/2)+1,b+n+1);
    mid=b[n/2+1];
    len=(int)sqrt(n);
    if (n%len) num=n/len+1;else num=n/len;
    for (int i=1;i<=num;i++)
    {
        l[i]=(i-1)*len+1;
        r[i]=min(n,i*len);
        sort(a+l[i],a+r[i]+1,cmp);
    }
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        L=x/len;
        if (x>r[L]) L++;
        R=y/len;
        if (y>r[R]) R++;
        if (L==R) add(L,x,y);else
        {   
            add(L,x,r[L]);add(R,l[R],y);
            for (int j=L+1;j<R;j++) tag[j]++;
        }
        sum=0;
        mid++;
        for (int j=1;j<=num;j++)
        {
            k=binary(l[j],r[j],mid-tag[j]);
            if (a[k].x>=mid-tag[j]) k--;
            if (k>=l[j]) sum+=k-l[j]+1;
        }
        if (sum>n/2) mid--;
        printf("%d\n",mid);
    }
}
Category: 分块 | Tags:
12
22
2015
0

【JSOI2015】字符串生成器

首先[tex]\sqrt n[/tex]枚举答案长度,在n枚举起点。接下来就是验证是否可行了。一开始想了一个错误的贪心,还自以为能A。。。

其实我们可以用区间DP来判断。

首先我们定义一下完美匹配。区间[i,j]是完美匹配当且仅当在用最优的方法删去一些模式串后,剩余的是模式串的前缀。显然如果区间长度是模式串的长度倍数又是完美匹配,那就符合条件。

完美匹配可以这样转移,将[i,j]分为[i,k],[k+1,j]只要两段都是完美匹配且至少有一段的长度是区间长度的倍数,这是一种转移。

还有一种转移是,[i,j-1]是完美匹配,且j处的字符为[i,j-1]的前缀的下一位,也就是模式串的第((j-i)%len+1)位,len是模式串的长度。

这样转移是l/len的复杂度的,加点乱七八糟的优化就能过了。

代码:

 

#include<set>
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
int T,l,ans;
int f[210][210];
set<string> st;
char s[510],str[510];
int compare(int x,int len)
{
    for (int i=0;i<len;i++)
        if (s[i+x]>str[i]) return 1;else
        if (s[i+x]<str[i]) return -1;
    return 0;
}
void judge(int len)
{
    if (len>ans) return;
    for (int L=0;L<l;L++)
    if (L+len-1==l) break;else
    if (s[L]==s[0]&&s[L+len-1]==s[l-1])
    {
        string s1;
        s1.clear();
        for (int i=0;i<len;i++)
            s1+=s[L+i];
        if (st.count(s1)) continue;
        st.insert(s1);
        memset(f,0,sizeof(f));
        for (int i=0;i<l;i++) if (s[i]==s[L]) f[i][i]=1;
        for (int p=2;p<=l;p++)
            for (int i=0;i<l-p+1;i++)
            {
                int j=i+p-1;
                f[i][j]|=f[i][j-1]&(s[L+(p-1)%len]==s[j]);
                if (f[i][j]) continue;
                for (int k=1;k<=p/len;k++)
                {
                    f[i][j]|=f[i][i+k*len-1]&f[i+k*len][j];
                    f[i][j]|=f[j-k*len+1][j]&f[i][j-k*len];
                    if (f[i][j]) break;
                }
            }
        if (f[0][l-1])
        {
            if ((len<ans)||(len==ans&&(compare(L,len)<0)))
            {
                ans=len;
                for (int i=0;i<len;i++)
                    str[i]=s[L+i];
            }
        }
    }
}
int main()
{
    scanf("%d",&T);
    while (T--)
    {
        scanf("%s",s);
        l=strlen(s);
        ans=l+1;
        st.clear();
        for (int i=1;i*i<=l;i++)
        if (l%i==0)
        {
            judge(i);
            if (i*i!=l) judge(l/i);
        }
        for (int i=0;i<ans;i++)
            printf("%c",str[i]);
        printf("\n");
    }
}
Category: Dynamic Programming | Tags:
12
20
2015
0

【bzoi2330】【SCOI2011】糖果

2330: [SCOI2011]糖果

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 3541  Solved: 1063
[Submit][Status][Discuss]

Description

幼儿园里有N个小朋友,lxhgww老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候,lxhgww需要满足小朋友们的K个要求。幼儿园的糖果总是有限的,lxhgww想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。

Input

输入的第一行是两个整数NK

接下来K行,表示这些点需要满足的关系,每行3个数字,XAB

如果X=1, 表示第A个小朋友分到的糖果必须和第B个小朋友分到的糖果一样多;

如果X=2, 表示第A个小朋友分到的糖果必须少于第B个小朋友分到的糖果

如果X=3, 表示第A个小朋友分到的糖果必须不少于第B个小朋友分到的糖果

如果X=4, 表示第A个小朋友分到的糖果必须多于第B个小朋友分到的糖果

如果X=5, 表示第A个小朋友分到的糖果必须不多于第B个小朋友分到的糖果;

Output

输出一行,表示lxhgww老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出-1

Sample Input

5 7
 
1 1 2
 
2 3 2
 
4 4 1
 
3 4 5
 
5 4 5
 
2 3 5
 
4 5 1
 

Sample Output


11
 

HINT

【数据范围】

对于30%的数据,保证 N<=100

对于100%的数据,保证 N<=100000

对于所有的数据,保证 K<=100000,1<=X<=5,1<=A, B<=N

裸的差分约束系统,只是照正常的写会TLE。hzwer:有一个坑爹的点就是形成的是十万个点的一条链,如果从0到所有点顺序加边就会T,改成倒序就AC。。。

代码:

 

#include<cstdio>
using namespace std;
int n,m,opt,x,y,edgenum;
int vet[500010],next[500010],head[100010],dis[500010],q[500010],flag[500010],inq[500010],Max[500010];
long long ans;
void add(int u,int v,int w)
{
	edgenum++;
	vet[edgenum]=v;
	next[edgenum]=head[u];
	head[u]=edgenum;
	dis[edgenum]=w;
}
bool spfa()
{
	int p1=-1,p2=0;
	q[0]=0;flag[0]=1;inq[0]++;
	while (p1<p2)
	{
		p1++;
		int u=q[p1%(n+1)];
		flag[u]=0;
		for (int e=head[u];e;e=next[e])
		{
			int v=vet[e];
			if (Max[u]+dis[e]>Max[v])
			{
				Max[v]=Max[u]+dis[e];
				if (!flag[v])
				{
					p2++;
					q[p2%(n+1)]=v;
					flag[v]=1;
					inq[v]++;
					if (inq[v]>n) return 0;
				}
			}
		}
	}
	return 1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&opt,&x,&y);
		if (opt==1){add(x,y,0);add(y,x,0);}else
		if (opt==2){add(x,y,1);}else
		if (opt==3){add(y,x,0);}else
		if (opt==4){add(y,x,1);}else{add(x,y,0);}
	}
	for (int i=n;i>=1;i--) add(0,i,1);
	if (!spfa()){printf("-1\n");return 0;}
	for (int i=1;i<=n;i++)
		ans+=Max[i];
	printf("%lld\n",ans);
}
Category: 差分约束 | Tags:
12
19
2015
0

【bzoj1856】【Scoi2010】字符串

1856: [Scoi2010]字符串

Time Limit: 5 Sec  Memory Limit: 64 MB
Submit: 1118  Solved: 598
[Submit][Status][Discuss]

Description

lxhgww最近接到了一个生成字符串的任务,任务需要他把n个1和m个0组成字符串,但是任务还要求在组成的字符串中,在任意的前k个字符中,1的个数不能少于0的个数。现在lxhgww想要知道满足要求的字符串共有多少个,聪明的程序员们,你们能帮助他吗?

Input

输入数据是一行,包括2个数字n和m

Output

输出数据是一行,包括1个数字,表示满足要求的字符串数目,这个数可能会很大,只需输出这个数除以20100403的余数

Sample Input

2 2

Sample Output

2

HINT

【数据范围】

对于30%的数据,保证1<=m<=n<=1000

对于100%的数据,保证1<=m<=n<=1000000

这是道好题啊。

我们先转化一下模型,我们先建一个平面直角坐标系,原点为(0,0),若当前位置选1,就横坐标加一,纵坐标加一,否则横坐标加一,纵坐标减一。由题意,目标点为(n+m,n-m),且不能到y=0以下的部分。所以,我们只要总方案数减去经过y=-1的方案数,易知总方案数为C(n+m,n)。

现在我们来讨论经过y=-1的方案数。

我们将第一次与y=-1的点的左边部分关于y=-1对称,最后方案数就等价于从(0,-2)走到(n+m,n-m)的方案数,我们设向上走了x步,向下走了y步,方案数即为(x+y,x),又x+y=n+m,x-y=n-m+2,解得x=n+1,y=m-1。

是不是感觉这方法很精妙啊,我也这么觉得啊。

代码:

 

#include<cstdio>
using namespace std;
typedef long long ll;
ll n,m,ans,mod=20100403;
ll ksm(ll x,ll y)
{
	ll ans=1;
	while (y)
	{
		if (y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}
ll calc(ll x,ll y)
{
	ll sum1=1,sum2=1,sum3=1;
	for (ll i=1;i<=x;i++)
		sum1=sum1*i%mod;
	for (ll i=1;i<=y;i++)
		sum2=sum2*i%mod;
	for (ll i=1;i<=x-y;i++)
		sum3=sum3*i%mod;
	sum1=sum1*ksm(sum2,mod-2)%mod;
	sum1=sum1*ksm(sum3,mod-2)%mod;
	return sum1;
}
int main()
{
	scanf("%lld%lld",&n,&m);
	ans=(calc(n+m,n)-calc(n+m,n+1)+mod)%mod;
	printf("%lld\n",ans);
}
Category: 数学 | Tags:

Host by is-Programmer.com | Power by Chito 1.3.3 beta | Theme: Aeros 2.0 by TheBuckmaker.com