12
8
2015
0

【JSOI2015】非诚勿扰

我们很容易想到的算法n4,枚举任意两个女该,算出她们对答案的贡献,因此再枚举每个人选的男孩,算出选这个男孩的概率,两者相乘就是贡献。选这个男孩的概率,就是这个男孩的概率除以选所有男孩的概率之和。

假如我们现在要求a这个女孩在共n个候选人中选第k个男孩(就是候选人从小到大第k个)的概率。

分子=(1-p)k-1*p+(1-p)k-1*p*(1-p)n+(1-p)k-1*p*(1-p)2n+……+(1-p)k-1*p*(1-p)oo

=(1-p)k-1*p*(((1-p)oo-1)/(-p))

=(1-p)k-1

多么完美的一个数啊!!

分母=(1-p)0+(1-p)1+(1-p)2+……+(1-p)n-1

=((1-p)n-1)/(-p)

这样就求出了第a个女孩选第k个男孩的概率。

有了这些精妙的公式,就很容易优化到了n2

接下来的优化很明显,先将读入的a排序,再用数据结构(如树状数组,线段树)去维护前缀和就可以了。

代码:

 

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,sum[500010],cnt,num[500010];
double p,q[500010],tr[500010],ans;
struct node{int a,b;}pai[500010];
bool cmp(const node x,const node y){if (x.a==y.a) return x.b<y.b;return x.a<y.a;}
void add(int x,double val)
{
    while (x<=n)
    {
        tr[x]+=val;
        x+=((x)&(-x));
    }
}
double ask(int x)
{
    if (!x) return 0;
    double ans=0;
    while (x)
    {
        ans+=tr[x];
        x-=((x)&(-x));
    }
    return ans;
}
double ksm(double x,int y)
{
    double ans=1;
    if (!y) return ans;
    while (y)
    {
        if (y&1) ans=ans*x;
        x=x*x;
        y>>=1;
    }
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    scanf("%lf",&p);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&pai[i].a,&pai[i].b);
        sum[pai[i].a]++;
    }
    sort(pai+1,pai+m+1,cmp);
    for (int i=1;i<=m;i++){if (pai[i].a!=pai[i-1].a) cnt=0;num[i]=++cnt;}
    for (int i=1;i<=n;i++)
    if (sum[i]) q[i]=(ksm(1-p,sum[i])-1)/(-p);
    for (int i=m;i>=1;i--)
    {
        ans+=ksm(1-p,num[i]-1)/q[pai[i].a]*ask(pai[i].b-1);
        add(pai[i].b,ksm(1-p,num[i]-1)/q[pai[i].a]);
    }
    printf("%.2lf\n",ans);
}
Category: 数学 | Tags:
12
6
2015
0

【bzoj1901】Zju2112 Dynamic Rankings

1901: Zju2112 Dynamic Rankings

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 5687  Solved: 2361
[Submit][Status][Discuss]

Description

给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。对于每一个询问指令,你必须输出正确的回答。 第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。分别表示序列的长度和指令的个数。第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。接下来的m行描述每条指令,每行的格式是下面两种格式中的一种。 Q i j k 或者 C i t Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t。

Input

对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。

Output

 

Sample Input

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

Sample Output

3
6

HINT

20%的数据中,m,n≤100; 40%的数据中,m,n≤1000; 100%的数据中,m,n≤10000。

升级版的主席树。本道题在普通的主席树上增加了修改这个操作,然而普通的主席树是无法接受的。因此我们想办法去改变主席树,考虑到一个数字序列,维护前缀我们用的是树状数组。以此类比,我们是否可以把一棵棵树当做一个个的数,然后用树状数组去维护,显然这是可行的,因此每改变一棵树,就要操作log次,因此修改的操作就变成了log2n的,对于本题的数据范围可以说轻取,询问操作也同理,一次询问需要log2n的复杂度。因此我们用nlog2n的总复杂K.O.了本题。

代码:

 

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,tot,cnt,t1,t2;
int a[10010],p[20010],x[10010],y[10010],z[10010],root[10010],L[10010],R[10010];
struct node{int l,r,sum;}tr[5000010];
char s[10];
void change(int l,int r,int &p,int x,int val)
{
    tr[++cnt]=tr[p];p=cnt;tr[p].sum+=val;
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) change(l,mid,tr[p].l,x,val);else change(mid+1,r,tr[p].r,x,val);
}
int ask(int l,int r,int k)
{
    if (l==r) return l;
    int sum1=0,sum2=0;
    for (int i=1;i<=t1;i++)
        sum1+=tr[tr[L[i]].l].sum;
    for (int i=1;i<=t2;i++)
        sum2+=tr[tr[R[i]].l].sum;
    int mid=(l+r)>>1;
    if (sum2-sum1>=k)
    {
        for (int i=1;i<=t1;i++)
            L[i]=tr[L[i]].l;
        for (int i=1;i<=t2;i++)
            R[i]=tr[R[i]].l;
        return ask(l,mid,k);
    }else
    {
        for (int i=1;i<=t1;i++)
            L[i]=tr[L[i]].r;
        for (int i=1;i<=t2;i++)
            R[i]=tr[R[i]].r;
        return ask(mid+1,r,k-sum2+sum1);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        p[++tot]=a[i];
    }
    for (int i=1;i<=m;i++)
    {
        scanf("%s",s);
        if (s[0]=='C'){scanf("%d%d",&x[i],&y[i]);p[++tot]=y[i];}else
        {scanf("%d%d%d",&x[i],&y[i],&z[i]);}
    }
    sort(p+1,p+tot+1);
    tot=unique(p+1,p+tot+1)-p-1;
    for (int i=1;i<=n;i++)
    {
        a[i]=lower_bound(p+1,p+tot+1,a[i])-p;
        for (int j=i;j<=n;j+=((j)&(-j)))
            change(1,tot,root[j],a[i],1);
    }
    for (int i=1;i<=m;i++)
    {
        if (z[i])
        {
            x[i]--;
            t1=t2=0;
            for (int j=x[i];j;j-=((j)&(-j)))
                L[++t1]=root[j];
            for (int j=y[i];j;j-=((j)&(-j)))
                R[++t2]=root[j];
            printf("%d\n",p[ask(1,tot,z[i])]);
        }else
        {
            y[i]=lower_bound(p+1,p+tot+1,y[i])-p;
            for (int j=x[i];j<=n;j+=((j)&(-j)))
                change(1,tot,root[j],a[x[i]],-1);
            for (int j=x[i];j<=n;j+=((j)&(-j)))
                change(1,tot,root[j],y[i],1);
            a[x[i]]=y[i];
        }
    }
}
Category: 主席树 | Tags:
12
5
2015
0

【bzoj3524】【Poi2014】Couriers

3524: [Poi2014]Couriers

Time Limit: 20 Sec  Memory Limit: 128 MB
Submit: 899  Solved: 300
[Submit][Status][Discuss]

Description

给一个长度为n的序列a。1≤a[i]≤n。
m组询问,每次询问一个区间[l,r],是否存在一个数在[l,r]中出现的次数大于(r-l+1)/2。如果存在,输出这个数,否则输出0。

 

Input

第一行两个数n,m。
第二行n个数,a[i]。
接下来m行,每行两个数l,r,表示询问[l,r]这个区间。

 

Output

m行,每行对应一个答案。

 

Sample Input

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

Sample Output

1
0
3
0
4
 

HINT

【数据范围】

n,m≤500000

Source

一道裸的可持久化线段树。询问时,如果左儿子的区间数字出现数大于(r-l+1)/2,那么右儿子的的区间数字出现数不会达到(r-l+1)/2,因此也不存在数出现数大于(r-l+1)/2,所以只要去访问左儿子就可以了,同理,如果右儿子的区间数字出现数大于(r-l+1)/2,就去访问右儿子,如果两者都不满足,就返回0。

解决啦!!

代码:

 

/**************************************************************
    Problem: 3524
    User: yeweining
    Language: C++
    Result: Accepted
    Time:6248 ms
    Memory:121900 kb
****************************************************************/
 
#include<cstdio>
using namespace std;
int n,m,a[500010],root[500010],l,r,cnt,tmp;
struct node{int l,r,sum;}tr[10000010];
void change(int l,int r,int &p,int x)
{
    tr[++cnt]=tr[p];p=cnt;tr[p].sum++;
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) change(l,mid,tr[p].l,x);else change(mid+1,r,tr[p].r,x);
}
int ask(int l,int r,int p1,int p2)
{
    if (l==r) return l;
    int mid=(l+r)>>1;
    if (tr[tr[p2].l].sum-tr[tr[p1].l].sum>tmp) return ask(l,mid,tr[p1].l,tr[p2].l);else
    if (tr[tr[p2].r].sum-tr[tr[p1].r].sum>tmp) return ask(mid+1,r,tr[p1].r,tr[p2].r);else return 0;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for (int i=1;i<=n;i++)
    {
        root[i]=root[i-1];
        change(1,n,root[i],a[i]);
    }
    while (m--)
    {
        scanf("%d%d",&l,&r);
        tmp=(r-l+1)>>1;
        printf("%d\n",ask(1,n,root[l-1],root[r]));
    }
}

 

Category: 可持久化线段树 | Tags:
12
4
2015
0

【bzoj3674】可持久化并查集加强版

3674: 可持久化并查集加强版

Time Limit: 15 Sec  Memory Limit: 256 MB
Submit: 1053  Solved: 381
[Submit][Status][Discuss]

Description

Description:
自从zkysb出了可持久化并查集后……
hzwer:乱写能AC,暴力踩标程
KuribohG:我不路径压缩就过了!
ndsf:暴力就可以轻松虐!
zky:……

n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0
请注意本题采用强制在线,所给的a,b,k均经过加密,加密方法为x = x xor lastans,lastans的初始值为0
0<n,m<=2*10^5


 

Input

 

Output

 

Sample Input

5 6
1 1 2
3 1 2
2 1
3 0 3
2 1
3 1 2

Sample Output

1
0
1

HINT

 

Source

好吧,我承认我确实打可持久化数据结构有点上瘾了,因为它的魅力着实让我欲罢不能。。。

回归本题。

此题我们只要维护每一次的并查集的fa数组,由于时间问题,我们还需要维护连通块中点的个数来按秩合并。要维护数组,我们可以用可持久化线段树来维护,线段树的叶子节点就是这个数组,这样就可以维护每次的并查集了。

代码:

 

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,opt,x,y,x1,x2,ans,cnt,root[210010];
struct node{int l,r,fa,num;}tr[15000010];
void change(int l,int r,int &p,int x,int fa,int num)
{
	tr[++cnt]=tr[p];p=cnt;
	if (l==r){tr[p].fa=fa;tr[p].num=num;return;}
	int mid=(l+r)>>1;
	if (x<=mid) change(l,mid,tr[p].l,x,fa,num);else change(mid+1,r,tr[p].r,x,fa,num);
}
int ask(int l,int r,int p,int x)
{
	if (l==r) return p;
	int mid=(l+r)>>1;
	if (x<=mid) return ask(l,mid,tr[p].l,x);else return ask(mid+1,r,tr[p].r,x);
}
int getfather(int p,int x)
{
	int t=ask(1,n,p,x);
	if (tr[t].fa==x) return t;else return getfather(p,tr[t].fa);
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		change(1,n,root[0],i,i,1);
	for (int i=1;i<=m;i++)
	{
		scanf("%d",&opt);
		root[i]=root[i-1];
		if (opt==1)
		{
			scanf("%d%d",&x,&y);
			x^=ans;y^=ans;
			x1=getfather(root[i],x);x2=getfather(root[i],y);
			if (tr[x1].fa==tr[x2].fa) continue;
			if (tr[x1].num>tr[x2].num) swap(x1,x2);
			change(1,n,root[i],tr[x1].fa,tr[x2].fa,0);
			change(1,n,root[i],tr[x2].fa,tr[x2].fa,tr[x2].num+tr[x1].num);
		}else
		if (opt==2)
		{
			scanf("%d",&x);
			x^=ans;
			root[i]=root[x];
		}else
		{
			scanf("%d%d",&x,&y);
			x^=ans;y^=ans;
			x1=getfather(root[i],x);x2=getfather(root[i],y);
			if (tr[x1].fa==tr[x2].fa) ans=1;else ans=0;
			printf("%d\n",ans);
		}
	}
}
Category: 可持久化线段树 | Tags:
12
3
2015
0

【bzoj3261】最大异或和

3261: 最大异或和

Time Limit: 10 Sec  Memory Limit: 512 MB
Submit: 902  Solved: 378
[Submit][Status][Discuss]

Description

     

给定一个非负整数序列 {a},初始长度为 N。       
有   M个操作,有以下两种操作类型:
 
1 、A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 N+1。
2 、Q l r x:询问操作,你需要找到一个位置 p,满足 l<=p<=r,使得:
 
a[p] xor a[p+1] xor ... xor a[N] xor x 最大,输出最大是多少。  

Input

第一行包含两个整数 N  ,M,含义如问题描述所示。   
第二行包含 N个非负整数,表示初始的序列 A 。 
 
接下来 M行,每行描述一个操作,格式如题面所述。   

Output

假设询问操作有 T个,则输出应该有 T行,每行一个整数表示询问的答案。

Sample Input

5 5
2 6 4 3 6
A 1
Q 3 5 4
A 4
Q 5 7 0
Q 3 6 6
对于测试点 1-2,N,M<=5 。
对于测试点 3-7,N,M<=80000 。
对于测试点 8-10,N,M<=300000 。
其中测试点 1, 3, 5, 7, 9保证没有修改操作。
对于 100% 的数据, 0<=a[i]<=10^7。

Sample Output

4
5
6

HINT

对于      100%  的数据,     0<=a[i]<=10^7  

首先我们令b[i]=a[1]^a[2]……^a[i]。因此题目中的a[p]^a[p+1]……^a[n]^x就等于x^b[n]^b[p-1],x^b[n]是定值,我们只要用贪心去得到最大值就可以了。因此我们只要把b数组建一个二进制trie树,然后可持久化,问题就迎刃而解了。

代码:

/**************************************************************
    Problem: 3261
    User: yeweining
    Language: C++
    Result: Accepted
    Time:4148 ms
    Memory:207836 kb
****************************************************************/
 
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,u,v,a[1000010],b[2000010],cnt,ans,x,y,root[5000010];
char s[10];
struct node{int sum,a[2];}tr[15000010];
void change(int &p,int dep,int x)
{
    tr[++cnt]=tr[p];p=cnt;tr[p].sum++;
    if (dep<0) return;
    int t=(x>>dep)&1;
    change(tr[p].a[t],dep-1,x);
}
void ask(int p1,int p2,int dep,int x)
{
    if (dep<0) return;
    int t=(x>>dep)&1;
    if (tr[tr[p2].a[t^1]].sum!=tr[tr[p1].a[t^1]].sum)
    {
        ans+=(1<<dep);
        ask(tr[p1].a[t^1],tr[p2].a[t^1],dep-1,x);
    }else ask(tr[p1].a[t],tr[p2].a[t],dep-1,x);
}
int main()
{
    scanf("%d%d",&n,&m);
    n++;
    for (int i=2;i<=n;i++)
        scanf("%d",&a[i]);
    for (int i=2;i<=n;i++)
        b[i]=b[i-1]^a[i];
    for (int i=1;i<=n;i++)
    {
        root[i]=root[i-1];
        change(root[i],25,b[i]);
    }
    while (m--)
    {
        scanf("%s",s);
        if (s[0]=='A')
        {
            scanf("%d",&x);
            n++;
            b[n]=(b[n-1]^x);
            root[n]=root[n-1];
            change(root[n],25,b[n]);
        }else
        {
            scanf("%d%d%d",&u,&v,&x);
            ans=0;
            ask(root[u-1],root[v],25,(x^b[n]));
            printf("%d\n",ans);
        }
    }
Category: 可持久化trie树 | Tags:
12
2
2015
0

【bzoj3207】花神的嘲讽计划Ⅰ

3207: 花神的嘲讽计划Ⅰ

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1065  Solved: 389
[Submit][Status][Discuss]

Description

背景
花神是神,一大癖好就是嘲讽大J,举例如下:
“哎你傻不傻的!【hqz:大笨J】”
“这道题又被J屎过了!!”
“J这程序怎么跑这么快!J要逆袭了!”
……
描述
这一天DJ在给吾等众蒟蒻讲题,花神在一边做题无聊,就跑到了一边跟吾等众蒟蒻一起听。以下是部分摘录:
1.
“J你在讲什么!”
“我在讲XXX!”
“哎你傻不傻的!这么麻烦,直接XXX再XXX就好了!”
“……”
2.
“J你XXX讲过了没?”
“……”
“那个都不讲你就讲这个了?哎你傻不傻的!”
“……”
DJ对这种情景表示非常无语,每每出现这种情况,DJ都是非常尴尬的。
经过众蒟蒻研究,DJ在讲课之前会有一个长度为N方案,我们可以把它看作一个数列;
同样,花神在听课之前也会有一个嘲讽方案,有M个,每次会在x到y的这段时间开始嘲讽,为了减少题目难度,每次嘲讽方案的长度是一定的,为K。
花神嘲讽DJ让DJ尴尬需要的条件:
在x~y的时间内DJ没有讲到花神的嘲讽方案,即J的讲课方案中的x~y没有花神的嘲讽方案【这样花神会嘲讽J不会所以不讲】。
经过众蒟蒻努力,在一次讲课之前得到了花神嘲讽的各次方案,DJ得知了这个消息以后欣喜不已,DJ想知道花神的每次嘲讽是否会让DJ尴尬【说不出话来】。
 

Input

第1行3个数N,M,K;
第2行N个数,意义如上;
第3行到第3+M-1行,每行K+2个数,前两个数为x,y,然后K个数,意义如上;

Output

对于每一个嘲讽做出一个回答会尴尬输出‘Yes’,否则输出‘No’

Sample Input

8 5 3
1 2 3 4 5 6 7 8
2 5 2 3 4
1 8 3 2 1
5 7 4 5 6
2 5 1 2 3
1 7 3 4 5

Sample Output

No
Yes
Yes
Yes
No

HINT

 

题中所有数据不超过2*10^9;保证方案序列的每个数字<=N
2~5中有2 3 4的方案,输出No,表示DJ不会尴尬
1~8中没有3 2 1的方案,输出Yes,表示DJ会尴尬
5~7中没有4 5 6的方案,输出Yes,表示DJ会尴尬
2~5中没有1 2 3的方案,输出Yes,表示DJ会尴尬
1~7中有3 4 5的方案,输出No,表示DJ不会尴尬

Source

又练了一道可持久化线段树。。。

只要把一开始i这个点的权值变为i~i+k-1这段的hash值。然后就非常容易了。

代码:

 

/**************************************************************
    Problem: 3207
    User: yeweining
    Language: C++
    Result: Accepted
    Time:5644 ms
    Memory:96900 kb
****************************************************************/
 
#include<cstdio>
using namespace std;
typedef unsigned long long ull;
int n,m,k,root[100010],x,y,x1,cnt;
int a[100010];
ull mi[100010],h[100010],x2,INF;
struct node{int l,r,sum;}tr[8000010];
void change(int &p,ull l,ull r,ull x)
{
    tr[++cnt]=tr[p];p=cnt;tr[p].sum++;
    if (l==r) return;
    ull mid=l/2+r/2;
    if ((l&1)&&(r&1)) mid++;
    if (x<=mid) change(tr[p].l,l,mid,x);else change(tr[p].r,mid+1,r,x);
}
bool ask(int p1,int p2,ull l,ull r,ull x)
{
    if (l==r) return (tr[p2].sum>tr[p1].sum);
    ull mid=l/2+r/2;
    if ((l&1)&&(r&1)) mid++;
    if (x<=mid) return ask(tr[p1].l,tr[p2].l,l,mid,x);else return ask(tr[p1].r,tr[p2].r,mid+1,r,x);
}
ull get(int i){return h[i+k-1]-h[i-1]*mi[k];}
int main()
{
    INF=18446744073709551615UL;
    scanf("%d%d%d",&n,&m,&k);
    for (int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    mi[0]=1;
    for (int i=1;i<=n;i++)
        mi[i]=mi[i-1]*10007;
    for (int i=1;i<=n;i++)
        h[i]=h[i-1]*10007+a[i];
    for (int i=1;i<=n-k+1;i++)
    {
        root[i]=root[i-1];
        change(root[i],0,INF,get(i));
    }
    while (m--)
    {
        scanf("%d%d",&x,&y);
        ull x2=0;
        for (int i=1;i<=k;i++)
        {
            scanf("%d",&x1);
            x2=x2*10007+x1;
        }
        if (ask(root[x-1],root[y-k+1],0,INF,x2)) printf("No\n");else printf("Yes\n");
    }
}
Category: 可持久化线段树 | Tags:
12
1
2015
0

【JSOI2015】字符串树

这是一道裸的可持久化trie树。这个方法就不多说了,详见“初探主席树”及“cout on a tree”。

代码:

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010
using namespace std;
int n,u,v,cnt,l,q;
int edgenum,vet[2*N],next[2*N],head[2*N],root[N];
int f[N],d[N],num[N],son[N],top[N];
struct node{int sum,son[30];}tr[10*N];
char str[15],s[N][15];
void add(int u,int v)
{
	edgenum++;
	vet[edgenum]=v;
	next[edgenum]=head[u];
	head[u]=edgenum;
}
void dfs(int u,int fa,int dep)
{
	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,u,dep+1);
			num[u]+=num[v];
			if (num[v]>maxnum){maxnum=num[v];son[u]=v;}
		}
	}
}
void dfs(int u,int number)
{
	top[u]=number;
	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);
	}
}
int getlca(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;
}
void change(int &p,int k,int dep)
{
	tr[++cnt]=tr[p];p=cnt;tr[p].sum++;
	if (dep==strlen(s[k])) return;
	change(tr[p].son[s[k][dep]-'a'],k,dep+1);
}
int ask(int p,int dep)
{
	if (p==0&&dep) return 0;
	if (dep==l) return tr[p].sum;
	return ask(tr[p].son[str[dep]-'a'],dep+1);
}
void dfs1(int u,int e)
{
	if (u!=1){root[u]=root[f[u]];change(root[u],(e+1)>>1,0);}
	for (int e=head[u];e;e=next[e])
	{
		int v=vet[e];
		if (v!=f[u]) dfs1(v,e);
	}
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<n;i++)
	{
		scanf("%d%d%s",&u,&v,s[i]);
		add(u,v);add(v,u);
	}
	dfs(1,0,1);
	dfs(1,1);
	dfs1(1,0);
	scanf("%d",&q);
	while (q--)
	{
		scanf("%d%d%s",&u,&v,str);
		l=strlen(str);
		printf("%d\n",ask(root[u],0)+ask(root[v],0)-2*ask(root[getlca(u,v)],0));
	}
}

还有一种我在练习赛上自己yy的一种方法。然而,有些复杂(我是有自知之明的),文字解释起来非常复杂,代码放着,有兴趣的人可以自己研究一下。

代码:

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010
using namespace std;
typedef long long ll;
int n,q,cnt,l;
int edgenum,Edgenum,vet[2*N],next[2*N],head[2*N],Vet[10*N],Next[10*N],Head[10*N],x[N],y[N],a[N],b[N],lca[N];
ll dis[2*N],Ask[10*N],P[10*N],p[10*N],ans[N],mi[20],w[N],c[N],d[N];
int f[N],de[N],num[N],son[N],top[N],tr[10*N];
char s[20];
void add(int u,int v,ll w)
{
	edgenum++;
	vet[edgenum]=v;
	next[edgenum]=head[u];
	head[u]=edgenum;
	dis[edgenum]=w;
}
void dfs(int u,int fa,int dep)
{
	f[u]=fa;
	de[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,u,dep+1);
			num[u]+=num[v];
			if (num[v]>maxnum){maxnum=num[v];son[u]=v;}
		}
	}
}
void dfs(int u,int number)
{
	top[u]=number;
	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);
	}
}
int getlca(int u,int v)
{
	while (top[u]!=top[v])
	{
		if (de[top[u]]<de[top[v]]) swap(u,v);
		u=f[top[u]];
	}
	if (de[u]<de[v]) return u;else return v;
}
void Add(int u,int v,ll w,ll opt)
{
	Edgenum++;
	Vet[Edgenum]=v;
	Next[Edgenum]=Head[u];
	Head[u]=Edgenum;
	Ask[Edgenum]=w;
	P[Edgenum]=opt;
}
int asksum(ll x)
{
	int ans=0;
	while (x)
	{
		ans+=tr[x];
		x-=((x)&(-x));
	}
	return ans;
}
void tradd(ll x,int val)
{
	while (x<=cnt)
	{
		tr[x]+=val;
		x+=((x)&(-x));
	}
}
void dfs(int u)
{
	for (int e=Head[u];e;e=Next[e])
	{
		int v=Vet[e];
		ans[v]+=(ll)asksum(Ask[e])*P[e];
	}
	for (int e=head[u];e;e=next[e])
	{
		int v=vet[e];
		if (v!=f[u])
		{
			tradd(dis[e],1);
			dfs(v);
			tradd(dis[e],-1);
		}
	}
}
int main()
{
	freopen("strings.in","r",stdin);
	freopen("strings.out","w",stdout);
	scanf("%d",&n);
	mi[0]=1;
	for (int i=1;i<=10;i++) mi[i]=mi[i-1]*31;
	for (int i=1;i<n;i++)
	{
		scanf("%d%d%s",&x[i],&y[i],s);
		l=strlen(s);
		w[i]=0;
		for (int j=0;j<l;j++)
			w[i]=w[i]*31+s[j]-'a'+1;
		w[i]=w[i]*mi[10-l];
		p[++cnt]=(ll)w[i];
		add(x[i],y[i],w[i]);add(y[i],x[i],w[i]);
	}
	dfs(1,0,1);
	dfs(1,1);
	scanf("%d",&q);
	for (int i=1;i<=q;i++)
	{
		scanf("%d%d%s",&a[i],&b[i],s);
		lca[i]=getlca(a[i],b[i]);
		c[i]=0;
		l=strlen(s);
		for (int j=0;j<l;j++)
			c[i]=c[i]*31+s[j]-'a'+1;
		c[i]=c[i]*mi[10-l];
		d[i]=c[i]+(mi[10-l]-1)/30*('z'-'a'+1);
		p[++cnt]=(ll)c[i]-1;
		p[++cnt]=(ll)d[i];
		Add(a[i],i,d[i],1);
		Add(b[i],i,d[i],1);
		Add(lca[i],i,d[i],-2);
		Add(a[i],i,c[i]-1,-1);
		Add(b[i],i,c[i]-1,-1);
		Add(lca[i],i,c[i]-1,2);
	}
	sort(p+1,p+cnt+1);
	cnt=unique(p+1,p+cnt+1)-p-1;
	for (int i=1;i<=edgenum;i++)
		dis[i]=lower_bound(p+1,p+cnt+1,dis[i])-p;
	for (int i=1;i<=Edgenum;i++)
		Ask[i]=lower_bound(p+1,p+cnt+1,Ask[i])-p;
	dfs(1);
	for (int i=1;i<=q;i++)
		printf("%lld\n",ans[i]);
}
Category: 可持久化trie树 | Tags:
12
1
2015
0

【JSOI2015】送礼物

听睡神说此题是一道分数规划题,虽然我并不知道分数规划是什么东东,反正好像是二分一下答案,然后将分母乘过去,然后再去解决。

首先易知当长度大于L小于等于R的时候,最大值和最小值一定在边界最优。然后等于L的情况特判一下就好了。

所以,先二分答案x,然后用RMQ处理长度为L的特殊情况。之后我们分两种情况。

①i为最大值,j为最小值。所以只要找到这样一对(i,j)(L<j-i+1<=R),(a[i]-a[j])/(j-i+K)>=x说明x是可行的,我们把分母乘过去,

a[i]-a[j]>=x*j-x*i+x*K

=>a[i]+x*i-(a[j]+x*j)>=x*K

所以我们只要令b[i]=a[i]+x*i,然后维护一个单调队列即可。

②i为最小值,j为最大值。与上相同,但此时的式子是

a[j]-a[i]/(j-i+K)>=x

=>a[j]-a[i]>=x*j-x*i+x*K

=>a[j]-x*j-(a[i]-x*i)>=x*K

此时我们只要令b[i]=a[i]-x*i,再维护一下单调队列就好了。

因此,我们以nlogn的复杂度完美解决了本题。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef double db;
struct node{int id;double x;}q[50010];
int T,n,K,L,R;
int a[50010],Max[50010][25],Min[50010][25],len[50010];
double eps=1e-6,b[50010];
int ask(int x,int y){int j=len[y-x+1];return max(Max[x][j],Max[y-(1<<j)+1][j])-min(Min[x][j],Min[y-(1<<j)+1][j]);}
bool judge(db x)
{
	int p1,p2;
	for (int i=1;i<=n;i++)
	if (i+L-1>n) break;else
		if ((db)ask(i,i+L-1)>=(L-1+K)*x) return true;
	p1=1,p2=0;
	for (int i=1;i<=n;i++)
		b[i]=(db)(a[i]-i*x);
	for (int i=L+1;i<=n;i++)
	{
		while (p1<=p2&&(i-q[p1].id+1>R)) p1++;
		while (p1<=p2&&q[p2].x>=b[i-L]) p2--;
		q[++p2].id=i-L;
		q[p2].x=b[i-L];
		if (b[i]-b[q[p1].id]>=x*K) return true;
	}
	for (int i=1;i<=n;i++)
		b[i]=(db)(a[i]+i*x);
	p1=1,p2=0;
	for (int i=L+1;i<=n;i++)
	{
		while (p1<=p2&&(i-q[p1].id+1>R)) p1++;
		while (p1<=p2&&q[p2].x<=b[i-L]) p2--;
		q[++p2].id=i-L;
		q[p2].x=b[i-L];
		if (b[q[p1].id]-b[i]>=x*K) return true;
	}
	return false;
}
void binary(db l,db r)
{
	if (r-l<=eps){printf("%.4lf\n",l);return;}
	db mid=(l+r)/2;
	if (judge(mid)) binary(mid,r);else binary(l,mid);
}
int main()
{
	for (int i=1;i<=50000;i++)
		for (int j=20;j>=0;j--)
		if ((1<<j)<=i){len[i]=j;break;}
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d%d%d%d",&n,&K,&L,&R);
		for (int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			Min[i][0]=a[i];
			Max[i][0]=a[i];
		}
		for (int j=1;j<=20;j++)
			for (int i=1;i<=n;i++)
			if (i+(1<<j)-1>n) break;else
			{
				Max[i][j]=max(Max[i][j-1],Max[i+(1<<(j-1))][j-1]);
				Min[i][j]=min(Min[i][j-1],Min[i+(1<<(j-1))][j-1]);
			}
		binary(0,1000);
	}
}
Category: 分数规划 | Tags:
12
1
2015
0

【JSOI2015】子集选取

结论题。

手算一下小数据,就可以轻松得到一个结论,然后大样例就过了,然后。。。就A了

结论为:2n*k

代码:

 

#include<cstdio>
#define mod 1000000007
using namespace std;
typedef long long ll;
ll n,k;
ll ksm(ll x,ll y)
{
	ll ans=1;
	while (y)
	{
		if (y%2==1) ans=ans*x%mod;
		x=x*x%mod;
		y=y/2;
	}
	return ans;
}
int main()
{
	scanf("%lld%lld",&n,&k);
	printf("%lld\n",ksm(2,n*k));
}
Category: 数学 | Tags:
11
29
2015
0

【bzoj2588】【SPOJ10628】Count on a tree

2588: Spoj 10628. Count on a tree

Time Limit: 12 Sec  Memory Limit: 128 MB
Submit: 2926  Solved: 667
[Submit][Status][Discuss]

Description

给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权。其中lastans是上一个询问的答案,初始为0,即第一个询问的u是明文。

Input

第一行两个整数N,M。
第二行有N个整数,其中第i个整数表示点i的权值。
后面N-1行每行两个整数(x,y),表示点x到点y有一条边。
最后M行每行两个整数(u,v,k),表示一组询问。

Output

M行,表示每个询问的答案。

Sample Input

8 5
105 2 9 3 8 5 7 7
1 2
1 3
1 4
3 5
3 6
3 7
4 8
2 5 1
0 5 2
10 5 3
11 5 4
110 8 2

Sample Output

2
8
9
105
7

HINT

HINT:
N,M<=100000
暴力自重。。。

Source

又一道主席树。

我们把Ti这棵线段树表示i到根节点的线段树,那么Ti可以由Tfa[i]得到,处理好线段树后,就可以解决询问了,对于每个询问u,v,k,只要根据tr[p1].sum+tr[p2].sum-tr[lca].sum-tr[fa[lca]].sum二分答案就可以了。

完美解决。

代码:

 

#include<cstdio>
#include<algorithm>
#define N 100010
using namespace std;
int n,n1,m,u,v,x,y,k,cnt,edgenum,ans;
int a[N],b[N],vet[2*N],next[2*N],head[N],f[N],d[N],son[N],num[N],top[N],root[N];
struct node{int l,r,sum;}tr[20*N];
void add(int u,int v)
{
	edgenum++;
	vet[edgenum]=v;
	next[edgenum]=head[u];
	head[u]=edgenum;
}
void dfs(int u,int fa,int dep)
{
	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,u,dep+1);
			num[u]+=num[v];
			if (num[v]>maxnum){maxnum=num[v];son[u]=v;}
		}
	}
}
void dfs(int u,int number)
{
	top[u]=number;
	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);
	}
}
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;
}
void change(int l,int r,int &p,int x)
{
	tr[++cnt]=tr[p];p=cnt;tr[p].sum++;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (x<=mid) change(l,mid,tr[p].l,x);else change(mid+1,r,tr[p].r,x);
}
int ask(int x,int y,int k)
{
	int p1=root[x],p2=root[y],p3=root[lca(x,y)],p4=root[f[lca(x,y)]];
	int l=1,r=n;
	while (l<r)
	{
		int mid=(l+r)>>1;
		int t=tr[tr[p1].l].sum+tr[tr[p2].l].sum-tr[tr[p3].l].sum-tr[tr[p4].l].sum;
		if (t>=k) {p1=tr[p1].l;p2=tr[p2].l;p3=tr[p3].l;p4=tr[p4].l;r=mid;}else
		{p1=tr[p1].r;p2=tr[p2].r;p3=tr[p3].r;p4=tr[p4].r;k-=t;l=mid+1;}
	}
	return l;
}
void dfs(int u)
{
	root[u]=root[f[u]];
	change(1,n,root[u],a[u]);
	for (int e=head[u];e;e=next[e])
	{
		int v=vet[e];
		if (v!=f[u]) dfs(v);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	n1=unique(b+1,b+n+1)-b-1;
	for (int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+n1+1,a[i])-b;
	for (int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	dfs(1,0,1);
	dfs(1,1);
	dfs(1);
	while (m--)
	{
		scanf("%d%d%d",&x,&y,&k);
		ans=b[ask(x^ans,y,k)];
		printf("%d",ans);
		if (m) printf("\n");
	}
}
Category: 主席树 | Tags:

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