码迷,mamicode.com
首页 > 其他好文 > 详细

2016wust暑假集训之题解汇总2

时间:2016-08-09 20:26:37      阅读:219      评论:0      收藏:0      [点我收藏+]

标签:

数据结构

bzoj4546(可持久化Trie)

技术分享
中文题题意我就不说了

解析: 可持久化Trie的模板题,详见注释
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
const int maxbit=19;
const int maxn=10500005;
int tr[500002];
struct PerTrie
{
    int id;
    int next[maxn][2],num[maxn];
    void init(){ id=next[0][0]=next[0][1]=num[0]=0; }//初始化
    int f(int x,int i){ return (x>>i)&1; } //判断x的第i位为0或1
    void Insert(int& rt,int pre,int x,int pos) //插入
    {
        rt=++id;
        next[rt][0]=next[pre][0]; //赋等
        next[rt][1]=next[pre][1];
        num[rt]=num[pre]+1; //数量加1
        if(pos==-1) return; 
        int d=f(x,pos);
        Insert(next[rt][d],next[pre][d],x,pos-1);
    }
    int MaxXor(int l,int r,int x)
    {
        int ret=0;
        for(int i=maxbit;i>=0;i--)
        {
            int d=f(x,i);
            int a=next[l][d^1],b=next[r][d^1]; 
            if(num[b]-num[a]>0) ret|=(1<<i),l=a,r=b; //判断是否存在
            else l=next[l][d],r=next[r][d];
        }
        return ret;
    }
    int MinNum(int l,int r,int x)
    {
        int ret=0;
        for(int i=maxbit;i>=0;i--)
        {
            int d=f(x,i);
            if(d) ret+=num[next[r][0]]-num[next[l][0]]; //比它小的加上
            l=next[l][d]; r=next[r][d];
        }
        ret+=num[r]-num[l];  //<=要加上它,<的话就不用了
        return ret;
    }
    int Kth(int l,int r,int k)
    {
        int ret=0;
        for(int i=maxbit;i>=0;i--)
        {
            int t=num[next[r][0]]-num[next[l][0]];
            if(t>=k) l=next[l][0],r=next[r][0];  //足够
            else ret|=(1<<i),l=next[l][1],r=next[r][1],k-=t;
        }
        return ret;
    }
}PT;
int main()
{
    int Q;
    scanf("%d",&Q);
    int type,l,r,x,cnt=0;
    tr[0]=0;
    PT.init();
    while(Q--)
    {
        scanf("%d",&type);
        if(type==1)
        {
            scanf("%d",&x);
            ++cnt;
            PT.Insert(tr[cnt],tr[cnt-1],x,maxbit); //插入新的值
        }
        else if(type==2)
        {
            scanf("%d%d%d",&l,&r,&x);
            printf("%d\n",PT.MaxXor(tr[l-1],tr[r],x)^x);
        }
        else if(type==3)
        {
            scanf("%d",&x);
            cnt-=x;
        }
        else if(type==4)
        {
            scanf("%d%d%d",&l,&r,&x);
            printf("%d\n",PT.MinNum(tr[l-1],tr[r],x));
        }
        else
        {
            scanf("%d%d%d",&l,&r,&x);
            printf("%d\n",PT.Kth(tr[l-1],tr[r],x));
        }
    }
    return 0;
}
View Code

bzoj2741(分块+可持久化Trie)

技术分享
题意中文我就不说了

解析: 分块+可持久化Trie,先得到前缀异或值,插入到Trie中,然后分块,对每一块,处理出dp[i][j](i代表第几块,j代表第几个位置),dp[i][j]代表以第i块开始的到j这个位置

的连续字串最大异或值。查询时,如果l,r不在同一块内,可以先查询l所在的块的后一个块到r的连续字串最大异或值,之前的dp就可以派上用场了,然后就是处理l到l所在块

的这段区间,取两者最大值即可。

代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=12005;
const int maxbit=31;
int N,M,A[maxn];
int tr[maxn];
struct PerTrie
{
    int next[10000005][2],num[10000005];
    int id;
    void init(){ id=next[0][0]=next[0][1]=num[0]=0; }
    int f(int x,int i){ return (x>>i)&1; }
    void Insert(int& rt,int pre,int x,int pos) //插入
    {
        rt=++id;
        next[rt][0]=next[pre][0];
        next[rt][1]=next[pre][1];
        num[rt]=num[pre]+1;
        if(pos==-1) return;
        int d=f(x,pos);
        Insert(next[rt][d],next[pre][d],x,pos-1);
    }
    int MaxXor(int l,int r,int x) //查询最大异或值,因为A[i]保存
    {                             //的是前缀异或值,所以得到的结果就是某一段区间的异或值
        int ret=0;
        for(int i=maxbit;i>=0;i--)
        {
            int d=f(x,i);
            int a=next[l][d^1],b=next[r][d^1];
            if(num[b]-num[a]>0) ret|=(1<<i),l=a,r=b;
            else l=next[l][d],r=next[r][d];
        }
        return ret;
    }
}PT;
int block,num,bel[maxn],dp[120][maxn]; //dp保存第几块到第几个数的区间最大异或值
void init()
{
    tr[0]=0;
    PT.init();
    for(int i=1;i<=N;i++) PT.Insert(tr[i],tr[i-1],A[i],maxbit); //插入
    block=(int)sqrt(N+0.5);
    num=N/block;
    if(N%block) num++; //加1
    memset(dp,0,sizeof(dp));
    bel[0]=0;
    for(int i=1;i<=N;i++) bel[i]=(i-1)/block+1; //记录下属于哪个块
    for(int i=1;i<=num;i++)
    {
        int st=(i-1)*block+1;
        for(int j=st;j<=N;j++)
        {
            dp[i][j]=max(dp[i][j-1],A[j]^A[st-1]); //可能是[st,j]这段区间
            dp[i][j]=max(dp[i][j],PT.MaxXor(tr[st-1],tr[j],A[j])); //再找最大的
        }
    }
}
int GetAns(int l,int r)
{
    l--;
    int s=bel[l],ret=0;
    if(bel[r]>s) ret=dp[s+1][r]; //查询从后面一个块开始的
    for(int i=l;i<=min(r,s*block);i++)
    {
        ret=max(ret,PT.MaxXor(tr[l-1],tr[r],A[i]));
    }
    return ret;
}
int main()
{
    scanf("%d%d",&N,&M);
    A[0]=0;
    int x;
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&x);
        A[i]=A[i-1]^x;
    }
    init();
    int last=0,l,r;
    while(M--)
    {
        scanf("%d%d",&l,&r);
        l=(l+(LL)last)%N+1;
        r=(r+(LL)last)%N+1;
        if(l>r) swap(l,r);
        //printf("%d %d\n",l,r);
        last=GetAns(l,r);
        printf("%d\n",last);
    }
    return 0;
}
View Code

poj2761(伸展树求名次)

技术分享
题意:查询一段区间的第k大值,而且题目保证查询区间是不存在包含关系。

解析:因为不存在包含关系,所以直接离线排序,向右插入元素,向右删除元素。利用treap树实现名次树的功能。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int INF=1e9+7;
const int maxn=100005;
int A[maxn],cnt;  //A数组保存数,cnt是节点标号,我是用数组模拟的
struct treap
{
    treap* son[2];  //左右儿子
    int v,s,r;
    treap(){ v=s=r=0; son[0]=son[1]=NULL; }
    treap(int nv,int nr);
    int rk(){ return son[0]->s+1; }  //排名,第几个数
    int cmp(int k)  //比较,如果相等返回-1,小于返回0,大于1
    {
        if(k==v) return -1;
        return k<v?0:1;
    }
    void pushup(){ s=son[0]->s+son[1]->s+1; }  //更新大小
}null,tr[maxn];
treap::treap(int nv,int nr)
{
    v=nv; r=nr;
    s=1;
    son[0]=son[1]=&null;
}
treap* NewNode(int x,int r)//建新节点
{
    tr[cnt]=treap(x,r);
    return tr+cnt++;
}
struct splaytree
{
    int Size;
    treap* root;
    splaytree(){ Size=0; root=&null; }
    void Rotate(treap* &t,int d)  //翻转操作
    {
        treap* p=t->son[d^1];
        t->son[d^1]=p->son[d];
        p->son[d]=t;
        t->pushup();  //要更新
        t=p;
        t->pushup();
    }
    void Insert(treap* &t,int x,int r) //插入
    {
        if(t==&null) //插入
        {
            t=NewNode(x,r); //申请新节点
            return;
        }
        int d=t->cmp(x);
        if(d==-1) d=1; //往右边走
        Insert(t->son[d],x,r); 
        if(t->son[d]->r > t->r) Rotate(t,d^1); //旋转
        t->pushup();
    }
    void Remove(treap* &t,int x) //删除
    {
        int d=t->cmp(x);
        if(d==-1)
        {
            if(t->son[0]==&null) t=t->son[1];
            else if(t->son[1]==&null) t=t->son[0];
            else
            {
                int d2=(t->son[0]->r > t->son[1]->r ? 1: 0);
                Rotate(t,d2);
                Remove(t->son[d2],x);
            }
        }
        else Remove(t->son[d],x);
        if(t!=&null) t->pushup();
    }
    int Query(treap* &t,int kth) //查询
    {
        if(t==&null||kth<=0||kth>t->s) return -1;
        int a=t->rk();
        if(kth==a) return t->v;
        else if(kth<a) return Query(t->son[0],kth);
        else return Query(t->son[1],kth-a);
    }
};
int N,M;
struct Ques
{
    int x,y,k,id;
    Ques(int x=0,int y=0,int k=0,int id=0):x(x),y(y),k(k),id(id){}
    bool operator < (const Ques& t) const
    {
        if(x!=t.x) return x<t.x;
        return y<t.y;
    }
}q[maxn];
int ans[maxn];
int main()
{
    scanf("%d%d",&N,&M);
        splaytree spt; cnt=0;
        for(int i=1;i<=N;i++) scanf("%d",&A[i]);
        int x,y,k;
        for(int i=0;i<M;i++) //输入
        {
            scanf("%d%d%d",&x,&y,&k);
            q[i]=Ques(x,y,k,i);
        }
        sort(q,q+M); //排序
        int f=1,r=1;
        for(int i=0;i<M;i++)
        {
            Ques& t=q[i];
            int x=t.x,y=t.y,k=t.k;
            for(;f<x;f++) if(f<r) spt.Remove(spt.root,A[f]);
            if(r<f) r=f;
            for(;r<=y;r++) spt.Insert(spt.root,A[r],rand());
            ans[t.id]=spt.Query(spt.root,k);  //保存答案
        }
        for(int i=0;i<M;i++) printf("%d\n",ans[i]);
    return 0;
}
View Code

hdu3487(伸展树分裂合并)

技术分享
题意:起初整个排列是1,2,,,N,然后有两种操作
CUT a b c 将[a,b]分离出来后插入到剩余的数的第c个位置之后
FLIP a b 将[a,b]翻转
求最后的排列。
 
解析:伸展树合并分裂。对于CUT操作,先把左边和右边的分裂出来,再合并在一起,再分成[1,c]和[c+1,k](k是剩余的个数,注意区间可能为空,特殊处理一下就好了),
再将[a,b]插入进去。对于FLIP操作,还是分裂,再将[a,b]翻转。最后将所有翻转标记下压。记录答案即可。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int INF=1e9+7;
const int maxn=300005;
int N,M,ans,A[maxn],cnt;  //A数组保存数,cnt是节点标号,我是用数组模拟的
struct treap
{
    treap* son[2];  //左右儿子
    int v,s,rev;
    treap(){ v=s=rev=0; son[0]=son[1]=NULL; }
    treap(int nv);
    int rk(){ return son[0]->s+1; }  //排名,第几个数
    int cmp(int k)  //比较,如果相等返回-1,小于返回0,大于1
    {
        if(k==rk()) return -1;
        return k<rk()?0:1;
    }
    void pushup(){ s=son[0]->s+son[1]->s+1; }  //更新大小
    void pushdown();  //处理懒惰标记
}null,tr[maxn];
treap::treap(int nv)
{
    v=nv;
    s=1;
    rev=0;
    son[0]=son[1]=&null;
}
void treap::pushdown()
{
    if(this==&null) return;
    if(rev)
    {
        swap(son[0],son[1]);
        son[0]->rev^=1;
        son[1]->rev^=1;
        rev=0;
    }
}
treap* NewNode(int x)
{
    tr[cnt]=treap(x);
    return tr+cnt++;
}
struct splaytree
{
    int Size;
    treap* root;
    splaytree(){ Size=0; root=&null; }
    void Rotate(treap* &t,int d)  //翻转操作
    {
        t->pushdown();
        treap* p=t->son[d^1];
        p->pushdown();
        t->son[d^1]=p->son[d];
        p->son[d]=t;
        t->pushup();
        t=p;
        t->pushup();
    }
    void Splay(treap* &t,int k)  //将第k大的节点伸展到根
    {
        t->pushdown();
        int d=t->cmp(k);
        if(d!=-1)
        {
            if(d) Splay(t->son[d],k- t->rk());
            else Splay(t->son[d],k);
            Rotate(t,d^1);
        }
        t->pushup();
    }
    void Build(treap* &t,int le,int ri)  //将N个数建成一棵树
    {
        if(le>ri) return;
        int mid=(le+ri)/2;
        t=NewNode(mid);
        Build(t->son[0],le,mid-1);
        Build(t->son[1],mid+1,ri);
        t->pushup();
    }
    void Cut(treap* &t,int a,int b,int c)
    {
        int len=b-a+1;
        if(len==N) return; //是整个区间就不用管了
        Splay(t,a); t->pushdown();  //分裂出左边的
        treap *L=t->son[0];
        L->pushdown();
        t->son[0]=&null; t->pushup();

        Splay(t,len); t->pushdown(); //分裂出右边的
        treap *R=t->son[1];
        R->pushdown();
        t->son[1]=&null; t->pushup();

        treap *nt;
        if(R!=&null) //左右合并
        {
            nt=R;
            Splay(nt,1);
            nt->son[0]=L; nt->pushup();
        }
        else
        {
            nt=L;
            Splay(nt,a-1);
            nt->son[1]=R; nt->pushup();
        }
        if(c+len==N) //在整个之后特殊处理一下就好
        {
            Splay(nt,c);
            Splay(t,1);
            t->son[0]=nt;
            t->pushup();
            return;
        }
        Splay(nt,c+1);
        treap *l=nt->son[0]; l->pushdown();
        nt->son[0]=&null; nt->pushup();
        t->son[1]=nt; t->pushup();
        Splay(t,1);
        t->son[0]=l; t->pushup();
    }
    void Reverse(treap* &t,int a,int b) //翻转
    {
        Splay(t,a); //左边
        treap *L=t->son[0];
        L->pushdown();
        t->son[0]=&null; t->pushup();
        Splay(t,b-a+1);  //右边
        treap *R=t->son[1];
        R->pushdown();
        t->son[1]=&null; t->pushup();
        t->rev^=1; //置翻转标记
        t->pushdown();
        t->son[0]=L; t->pushup();
        Splay(t,b);
        t->son[1]=R; t->pushup();
    }
    void PushAll(treap* &t) //中序遍历
    {
        if(t==&null) return;
        t->pushdown();
        PushAll(t->son[0]);
        A[++ans]=t->v;
        PushAll(t->son[1]);
        t->pushup();
    }
};
int main()
{
    while(scanf("%d%d",&N,&M)!=EOF)
    {
        if(N<0&&M<0) break;
        splaytree spt; cnt=0;
        spt.Build(spt.root,1,N); //建树
        int a,b,c;
        char op[10];
        while(M--)
        {
            scanf("%s",op);
            if(op[0]==C) //CUT操作
            {
                scanf("%d%d%d",&a,&b,&c);
                spt.Cut(spt.root,a,b,c);
            }
            else  //FLIP操作
            {
                scanf("%d%d",&a,&b);
                spt.Reverse(spt.root,a,b);
            }

        }
        ans=0;
        spt.PushAll(spt.root); //整个下压
        for(int i=1;i<=ans;i++)
            printf("%d%c",A[i],i==ans?\n: );
    }
    return 0;
}
View Code

hdu3436(伸展树)

技术分享
题意:刚开始给出一个N,代表初始是1,2,3...N排成一行,有三种操作 Top x 将值x置于最前面 Query x 查询值x排在第几 Rank x 查询排在第x的位置是数值几

解析:这道题坑了我半天,一直超时,看了别人的博客,原来是自己 的Splay写low了,而且要加一个地方才能不超时,我代码中有注释。 数据达到10^8,显然不能建这么大一颗树,需要离散化,把Top和Query操作 的数保存下来离散化,然后处理出一段段区间 一开始按照区间段排在第几个的位置建树,要保存区间段v对应的是哪个节点的 编号,如果是Top操作,先二分找到x对应的区间k,将k对应的节点伸展到根,然后 删除这个节点,再插入最右边,如果是Query操作,也是二分找到x对应的区间k, 将k对应的节点伸展到根,即可得到他的排名。如果是Rank操作,从根开始找就行, 判断是否在某一段区间内。然后找到了就可以得到答案了。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
#define t tr[r]
#define y tr[fa]
const int maxn=200005;
int N,Q,A[maxn];//A数组用于保存离散化的数
char op[maxn][7]; //操作字符串
int opx[maxn],seg[maxn][2],Size;//操作值,区间左右端点,多少个区间
int BIS(int v) //二分找区间下标
{
    int x=1,yy=Size,mid;
    while(x<=yy)
    {
        int mid=(x+yy)/2;
        if(seg[mid][0]<=v&&v<=seg[mid][1]) return mid; //找到了
        if(seg[mid][1]<v) x=mid+1;
        else yy=mid;
    }
}
int bel[maxn]; //用于保存第几段区间现在的节点编号
struct treap
{
    int son[2],fa; //左右儿子和父亲
    int s,v,num; //s是大小,v代表区间的下标,num代表这段区间的大小
    treap(){ s=v=num=0; son[0]=son[1]=fa=0; }
    int rk();//排名
    void pushup(); //更新
    int cmp(int k); //比较
}tr[maxn*2];
int treap::rk(){ return tr[son[0]].s+num; }
void treap::pushup(){ s=tr[son[0]].s+tr[son[1]].s+num; }
int treap::cmp(int k)
{
    if(tr[son[0]].s<k&&k<=rk()) return -1;//在区间内
    if(k<rk()) return 0;
    else return 1;
}
struct splaytree
{
    int id,root;
    void init(){ id=root=0; tr[0].s=tr[0].num=0; }//初始化
    void dfs(int r)
    {
        if(r==0) return;
        dfs(t.son[0]);
        printf("%d ",t.v);
        dfs(t.son[1]);
    }
    void Visit(){ dfs(root); puts("");}
    int NewNode(int fa,int v) //得到新节点
    {
        bel[v]=++id; //保存v值对应的下标
        int r=id;
        t.fa=fa; t.v=v;
        t.s=t.num=seg[v][1]-seg[v][0]+1;
        t.son[0]=t.son[1]=0;
        return r;
    }
    void Build(int &r,int le,int ri,int fa)//建树
    {
        if(le>ri) return; //区间不存在
        int mid=(le+ri)/2;
        r=NewNode(fa,mid); //得到新节点
        Build(t.son[0],le,mid-1,r); //左建树
        Build(t.son[1],mid+1,ri,r); //右建树
        t.pushup();
    }
    void Insert(int &r,int fa,int k) //插入到最左边
    {
        if(r==0){ r=NewNode(fa,k); return; } //在最左边建立新节点
        Insert(t.son[0],r,k);
        t.pushup();
    }
    int GetMin(int r) //得到这棵子树最左端的节点
    {
        while(t.son[0]!=0) r=t.son[0];
        return r;
    }
    void Rotate(int r,int d) //翻转
    {
        int fa=t.fa;
        y.son[d^1]=t.son[d];
        if(t.son[d]!=0) tr[t.son[d]].fa=fa;
        if(y.fa==0) t.fa=0;
        else if(tr[y.fa].son[0]==fa) { t.fa=y.fa; tr[t.fa].son[0]=r; }
        else if(tr[y.fa].son[1]==fa) { t.fa=y.fa; tr[t.fa].son[1]=r; }
        t.son[d]=fa;
        y.fa=r;
        y.pushup();
        t.pushup();

    }
    void Splay(int r) //伸展
    {
        while(t.fa!=0)
        {
            if(tr[t.fa].fa==0) Rotate(r,tr[t.fa].son[0]==r);
            else
            {
                int fa=t.fa;
                int d=(tr[y.fa].son[0]==fa);
                if(y.son[d]==r)
                {
                    Rotate(r,d^1);
                    Rotate(r,d);
                }
                else
                {
                    Rotate(fa,d);
                    Rotate(r,d);
                }
            }
        }
    }
    void Top(int &r,int k) //将k对应的区间置于最前面
    {
        r=bel[k];
        Splay(r);//伸展到根
        int rr=GetMin(t.son[1]);//找到右子树最小的节点
        Splay(rr); //伸展到根
        treap& tt=tr[rr];
        tt.son[0]=t.son[0];  //将原来的左子树连到rr上去
        if(t.son[0]!=0) tr[t.son[0]].fa=rr;
        tt.fa=0;
        tt.pushup();
        r=rr;
        Insert(r,0,k); //插入到最右边
        Splay(r=id); //不加这个会超时。。。
    }
    int Query(int &r,int k)
    {
        r=bel[k];
        Splay(r);
        return t.rk();
    }
    int Rank(int &r,int k)
    {
        int d=t.cmp(k);
        if(d==-1) return seg[t.v][0]+ k- tr[t.son[0]].s - 1;
        if(d==0) return Rank(t.son[0],k);
        else return Rank(t.son[1],k- t.rk());
    }
}spt;
int main()
{
    int T,Case=0;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&N,&Q);
        int k=0;
        A[k++]=0;
        for(int i=0;i<Q;i++)
        {
            scanf("%s%d",op[i],&opx[i]);
            if(op[i][0]==T||op[i][0]==Q) A[k++]=opx[i];
        }
        A[k++]=N+1;
        sort(A,A+k);
        Size=0;
        for(int i=1;i<k;i++) //处理出每段区间
        {
            if(A[i]==A[i-1]) continue;
            if(A[i]-A[i-1]>1){ seg[++Size][0]=A[i-1]+1; seg[Size][1]=A[i]-1; }
            seg[++Size][0]=A[i]; seg[Size][1]=A[i];
        }
        spt.init();
        spt.Build(spt.root,1,Size,0);
        printf("Case %d:\n",++Case);
        for(int i=0;i<Q;i++)
        {
           if(op[i][0]==T) spt.Top(spt.root,BIS(opx[i]));
           else if(op[i][0]==Q) printf("%d\n",spt.Query(spt.root,BIS(opx[i])));
           else printf("%d\n",spt.Rank(spt.root,opx[i]));
        }
    }
    return 0;
}
View Code

poj2104(主席树)

技术分享
题意;查询区间内第k大的数

解析:裸的主席树,详见代码实现,我给了注释

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
const int maxn=100005;
int N,M,A[maxn],B[maxn];
struct Tree
{
    Tree *lson,*rson;
    int a,s;  //a这个数的个数,s是<=这个数的个数
    Tree(){ a=s=0; lson=rson=NULL; }
    void pushup()
    {
        s=a;
        if(lson) s+=lson->s;
        if(rson) s+=rson->s;
    }
}*null=new Tree(),*r[maxn]={NULL},data[maxn*20];
int tree_id;
void Update(Tree* &y,Tree* &x,int le,int ri,int v)
{
    if(x==NULL) x=null; 
    y=&data[++tree_id];  //创建新节点
    *y=Tree();
    int mid=(le+ri)/2;
    if(le==ri)  //到达叶子节点
    {
        *y=*x;  //有可能x并不是null,而且s和a也不一定为0
        y->s++; y->a++;  //加1
        return;
    }
    if(v<=B[mid])  //在左边
    {
        Update(y->lson,x->lson,le,mid,v);
        y->rson=x->rson; //右边是完全一样的,为了节约空间
        y->pushup();   //更新一下
    }
    else   //右边同理
    {
        Update(y->rson,x->rson,mid+1,ri,v);
        y->lson=x->lson;
        y->pushup();
    }
}
int Query(Tree* &x,Tree* &y,int le,int ri,int kth)
{
    if(x==NULL) x=null;
    if(y==NULL) y=null;
    if(le==ri) return B[le];  //找到了
    int mid=(le+ri)/2;
    int cnt=0;
    if(y->lson) cnt+=y->lson->s;  //先加整段到y的
    if(x->lson) cnt-=x->lson->s;  //减掉到x-1的
    if(cnt>=kth) return Query(x->lson,y->lson,le,mid,kth); //左边足够
    else return Query(x->rson,y->rson,mid+1,ri,kth-cnt);  //右边
}
int main()
{
    null->lson=null; null->rson=null;//指向自己
    tree_id=0;   
    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);
    int k=1;
    for(int i=2;i<=N;i++) if(B[i]!=B[k]) B[++k]=B[i]; //离散化
    for(int i=1;i<=N;i++) Update(r[i],r[i-1],1,k,A[i]); //更新
    for(int i=1;i<=M;i++)
    {
        int x,y,kth;  //查询[x,y]第kth的数
        scanf("%d%d%d",&x,&y,&kth);
        printf("%d\n",Query(r[x-1],r[y],1,k,kth));
    }
    return 0;
}
View Code

主席树的数组写法

技术分享
int hd[maxn];
struct Tree{ int lson,rson,cnt; };
struct CMTree
{
   int id;
   Tree tree[maxn*40];
   void init(){ id=0; }
   void pushup(int rt){ e.cnt=tree[e.lson].cnt+tree[e.rson].cnt; }
   void Build_tree(int& rt,int le,int ri)
   {
       rt=id++;
       e.cnt=0;
       if(le==ri) return;
       int mid=(le+ri)/2;
       Build_tree(e.lson,le,mid);
       Build_tree(e.rson,mid+1,ri);
       pushup(rt);
   }
   void Update(int pre,int& rt,int le,int ri,int k,int d)
   {
       rt=id++;
       if(le==ri){ e.cnt=p.cnt+d; return; }
       int mid=(le+ri)/2;
       if(k<=mid)
       {
           Update(p.lson,e.lson,le,mid,k,d);
           e.rson=p.rson;
       }
       else
       {
           Update(p.rson,e.rson,mid+1,ri,k,d);
           e.lson=p.lson;
       }
       pushup(rt);
   }
   int Query(int rt,int le,int ri,int x,int y)
   {
       if(x<=le&&ri<=y) return e.cnt;
       int mid=(le+ri)/2;
       int ret=0;
       if(x<=mid) ret+=Query(e.lson,le,mid,x,y);
       if(y>mid)  ret+=Query(e.rson,mid+1,ri,x,y);
       return ret;
   }

}CT;
View Code

RMQ模板

技术分享
//去搜一下别人给的解释
int rmq[maxn][20],Lg[maxn];
void GetLg()
{
    Lg[0]=-1;
    for(int i=1;i<maxn;i++)
        Lg[i]=Lg[i-1]+(i&(i-1)?0:1);
}
int f(int x){ return 1<<x; }
void ST(int N)
{
    for(int i=0;i<N;i++) rmq[i][0]=A[i];
    for(int j=1;f(j)<=N;j++)
       for(int i=0;i+f(j)-1<N;i++)
        rmq[i][j]=max(rmq[i][j-1],rmq[i+f(j-1)][j-1]);
}
int RMQ(int x,int y)
{
    if(x>y) swap(x,y);
    int k=Lg[y-x+1];
    return max(rmq[x][k],rmq[y-f(k)+1][k]);
}
View Code

hdu5008(后缀数组)

技术分享
题意:找到字符串中第k小的字串,并且输出下标最小的那一个。
 
解析:用后缀数组处理,每个后缀不同的子串个数是len-sa[i]-h[i],所以可以二分去找,但是找到了并不一定是下标最小的,所以还要继续往后面更新。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
typedef __int64 LL;
typedef pair<int,int> par;
const int maxn=100005;
char S[maxn];
int A[maxn];
LL sum[maxn];
int wa[maxn],wb[maxn],wv[maxn],WS[maxn],sa[maxn];
bool 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 n,int m)  //模板
{
    int i,j,p;
    int *x=wa,*y=wb;
    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(p=1,j=1;p<n;j<<=1,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];
        swap(x,y);
        for(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++;
    }
}
int rk[maxn],h[maxn];
void GetHeight(int *r,int n)
{
    for(int i=0;i<=n;i++) rk[sa[i]]=i;
    int k=0;
    h[0]=0;
    for(int i=0;i<n;i++)
    {
        if(k) k--;  //先减1
        int j=sa[rk[i]-1];//排名在前面的
        while(r[i+k]==r[j+k]) k++; //相同一直加
        h[rk[i]]=k;
    }
}
int BIS(int x,int y,LL k)//找到第一个大于k的位置
{
    while(x<=y)
    {
        int mid=(x+y)/2;
        if(sum[mid]>=k) y=mid-1;
        else x=mid+1;
    }
    return y+1;
}
int main()
{
    while(scanf("%s",S)!=EOF)
    {
        int len=strlen(S);
        for(int i=0;i<len;i++) A[i]=S[i];
        A[len]=0;
        DA(A,len+1,130); //后缀数组处理
        GetHeight(A,len); //得到lcp值
        sum[0]=0;
        for(int i=1;i<=len;i++)
        {
            int a=sa[i];
            int b=h[i];
            sum[i]=sum[i-1]+len-a-b; //计算不同子串个数
        }
        int Q;
        LL l=0,r=0,v;
        scanf("%d",&Q);
        while(Q--)
        {
            scanf("%lld",&v);
            LL k=(l^r^v)+1;
            if(k>sum[len]) printf("%lld %lld\n",l=0,r=0); //无解的情况
            else
            {
                int p=BIS(1,len,k); //二分找
                l=sa[p];
                int sl=h[p]+k-sum[p-1]; //长度
                int cur=p;
                while(++cur<=len&&h[cur]>=sl) l=min(l,(LL)sa[cur]);//往后面找位置最小的,不知道这种方式为何不会超时
                r=l+sl-1;
                l++; r++;
                printf("%lld %lld\n",l,r);
            }
        }
    }
    return 0;
}
View Code

bzoj2049(动态树lct模板题)

技术分享
题意: 有三种操作,建边,毁边以及查询两个点是否相通,保证任意两个点之间最多只有一条相通的路径,也就是说整个图总是树或森林。

解析: 动态树模板题,另附两个介绍动态树的网址:

https://oi.abcdabcd987.com/summary-of-link-cut-tree/

http://wenku.baidu.com/view/75906f160b4e767f5acfcedb.html

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=10005;
struct lct
{
    lct *fa,*son[2];
    int rev;
    void pushdown() //把延迟更新的标记清除掉
    {
        if(!rev) return;  
        swap(son[0],son[1]); //翻转,交换左右儿子
        son[0]->rev^=1; son[1]->rev^=1; 
        rev=0;
    }
};
struct LCT
{
    lct data[maxn];
    lct *null;
    void init(int Size=maxn-1) //初始化
    {
        null=data; //null指向首元素
        for(int i=0;i<=Size;i++)
            data[i].son[0]=data[i].son[1]=data[i].fa=null;
    }
    bool Same(lct* x,lct* &y) //判断x和x的父亲是否在同一树里
    {
        return (y=x->fa)!=null&&(y->son[0]==x||y->son[1]==x);
    }
    void Rotate(lct* x,int d) //翻转
    {
        lct* y=x->fa; //x的父亲
        y->son[d^1]=x->son[d];
        if(x->son[d]!=null) x->son[d]->fa=y; //x的子节点的父亲指向y
        x->fa=y->fa;  //连接
        if(y->fa->son[0]==y) x->fa->son[0]=x;
        else if(y->fa->son[1]==y) x->fa->son[1]=x;
        x->son[d]=y;
        y->fa=x;
    }
    void Splay(lct* x)
    {
        x->pushdown(); //清除标记
        lct* y;
        while(Same(x,y)) //没有到树的最顶点
        {
            y->pushdown(); 
            x->pushdown();
            Rotate(x,y->son[0]==x); //翻转
        }
    }
    lct* Access(lct* u)  //打通路径
    {
        lct *v=null;
        for(;u!=null;u=u->fa)
        {
            Splay(u);
            u->son[1]=v;
            v=u;
        }
        return v;
    }
    lct* GetRoot(lct* x) //得到根
    {
        for(x=Access(x);x->pushdown(),x->son[0]!=null;x=x->son[0]);
        return x;
    }
    void MakeRoot(lct* x) //使x成为根
    {
        Access(x)->rev^=1;
        Splay(x);
    }
    void Link(lct* x,lct* y) //连接两个点
    {
        MakeRoot(x);
        x->fa=y;
        Access(x);
    }
    void Cut(lct* x,lct* y) //断开两个点
    {
        MakeRoot(x);
        Access(y);
        Splay(y);
        y->son[0]->fa=null;
        y->son[0]=null;
    }
}A;
int N,M;
int main()
{
    scanf("%d%d",&N,&M);
    A.init(N);
    int x,y;
    char S[10];
    while(M--)
    {
        scanf("%s%d%d",S,&x,&y);
        if(S[0]==Q)
        {
            lct *a=A.GetRoot(A.data+x);
            lct *b=A.GetRoot(A.data+y);
            if(a!=A.null&&a==b) printf("Yes\n");
            else printf("No\n");
        }
        else if(S[0]==D) A.Cut(A.data+x,A.data+y);
        else A.Link(A.data+x,A.data+y);
    }
    return 0;
}
View Code

spoj QTREE(树链剖分)

技术分享
题意:给出一棵树,有两种操作,一种是修改边权,另一种是查询两个点之间最大边权。

解析:裸的树链剖分,用线段树维护区间最大值。

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define tu nod[u]
#define tv nod[v]
#define e tree[id]
#define lson tree[id*2]
#define rson tree[id*2+1]
const int maxn=10005;
int N,pid;
vector<int> G[maxn];
struct node
{
    int top,fa,deep;//top所属树链的顶点,fa父节点,deep深度
    int s,p,fp,son; //s以它为子树的大小,p新编号,fp相对p反向的,这题里面没什么用,son重链所指向的点
}nod[maxn];
struct edge
{
    int u,v,c;
    edge(int u=0,int v=0,int c=0):u(u),v(v),c(c){}
}E[maxn];
void init()
{
    pid=0;
    for(int i=0;i<maxn;i++) G[i].clear(),nod[i].son=-1; //初始化
}
void dfs(int u,int fa,int deep)
{
    tu.fa=fa,tu.deep=deep,tu.s=1;  //保存父节点,深度,大小为1
    int Size=G[u].size();
    for(int i=0;i<Size;i++)
    {
        int v=G[u][i];
        if(v==fa) continue;  //父亲不管
        dfs(v,u,deep+1); 
        tu.s+=tv.s;  //加上大小
        if(tu.son==-1||tv.s>nod[tu.son].s) tu.son=v; //找重链所指向的点
    }
}
void Div(int u,int top)
{
    tu.top=top;  //重链顶点
    tu.p=++pid;  //重新编号
    nod[tu.p].fp=u;  
    if(tu.son!=-1) Div(tu.son,top); //有重链继续往下找
    else return;
    int Size=G[u].size();
    for(int i=0;i<Size;i++)
    {
        int v=G[u][i];
        if(v==tu.fa||v==tu.son) continue;  
        Div(v,v); //新的重链
    }
}
struct Tree   //线段树维护区间最大值
{
    int le,ri,v;
}tree[4*maxn];
void pushup(int id){ e.v=max(lson.v,rson.v); }
void Build_tree(int id,int le,int ri)
{
    e.le=le,e.ri=ri,e.v=0;
    if(le==ri) return;
    int mid=(le+ri)/2;
    Build_tree(id*2,le,mid);
    Build_tree(id*2+1,mid+1,ri);
}
void Update(int id,int k,int v)
{
    int le=e.le,ri=e.ri;
    if(le==ri){ e.v=v; return; }
    int mid=(le+ri)/2;
    if(k<=mid) Update(id*2,k,v);
    else Update(id*2+1,k,v);
    pushup(id);
}
int Query(int id,int x,int y)
{
    int le=e.le,ri=e.ri;
    if(x<=le&&ri<=y) return e.v;
    int mid=(le+ri)/2;
    int ret=0;
    if(x<=mid) ret=max(ret,Query(id*2,x,y));
    if(y>mid)  ret=max(ret,Query(id*2+1,x,y));
    return ret;
}
int Find(int u,int v)
{
    int f1=tu.top,f2=tv.top;  
    int ret=0;
    while(f1!=f2) //不在同一条链上
    {
        if(nod[f1].deep<nod[f2].deep)  //总是让u是深度大的点
        {
            swap(f1,f2);
            swap(u,v);
        }
        ret=max(ret,Query(1,nod[f1].p,tu.p));  //查询这条链
        u=nod[f1].fa; f1=tu.top;   //跳到父节点去
    }
    if(u==v) return ret;  //相同
    if(tu.deep>tv.deep) swap(u,v); 
    ret=max(ret,Query(1,nod[tu.son].p,tv.p)); //查询同一条链上的这段区间
    return ret;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&N);
        init();
        for(int i=1;i<N;i++)
        {
            int u,v,c;
            scanf("%d%d%d",&u,&v,&c);
            E[i]=edge(u,v,c);   //
            G[u].push_back(v);   
            G[v].push_back(u);
        }
        dfs(1,0,0); 
        Div(1,1);
        Build_tree(1,1,pid); //建树
        for(int i=1;i<N;i++)
        {
            edge& t=E[i];
            int& u=t.u;
            int& v=t.v;
            if(tu.deep>tv.deep) swap(u,v);
            Update(1,tv.p,t.c);   //修改边权
        }
        char op[15];
        while(scanf("%s",op)!=EOF)
        {
            int u,v;
            if(op[0]==D) break;
            scanf("%d%d",&u,&v);
            if(op[0]==Q) printf("%d\n",Find(u,v));  //查询
            else Update(1,nod[E[u].v].p,v); //更新
        }
    }
    return 0;
}
View Code

 

 

 

搜索

精确覆盖DLX算法模板

技术分享
struct DLX
{
    int n,id;
    int L[maxn],R[maxn],U[maxn],D[maxn];
    int C[maxn],S[maxn],loc[maxn][2];
    int H[ms];
    void init(int nn=0) //传列长
    {
        n=nn;
        for(int i=0;i<=n;i++) U[i]=D[i]=i,L[i]=i-1,R[i]=i+1;
        L[0]=n; R[n]=0;
        id=n;
        memset(S,0,sizeof(S));
        memset(H,-1,sizeof(H));
    }
    void Link(int x,int y)
    {
        ++id;
        D[id]=y; U[id]=U[y];
        D[U[y]]=id; U[y]=id;
        loc[id][0]=x,loc[id][1]=y;
        C[id]=y; S[y]++;
        if(H[x]==-1) H[x]=L[id]=R[id]=id;
        else
        {
            int a=H[x];
            int b=R[a];
            L[id]=a; R[a]=id;
            R[id]=b; L[b]=id;
            H[x]=id;
        }
    }
    void Remove(int c)
    {
        L[R[c]]=L[c];
        R[L[c]]=R[c];
        for(int i=D[c];i!=c;i=D[i])
            for(int j=R[i];j!=i;j=R[j])
        {
            U[D[j]]=U[j];
            D[U[j]]=D[j];
            S[C[j]]--;
        }
    }
    void Resume(int c)
    {
        for(int i=U[c];i!=c;i=U[i])
            for(int j=R[i];j!=i;j=R[j])
        {
            S[C[j]]++;
            U[D[j]]=j;
            D[U[j]]=j;
        }
        L[R[c]]=c;
        R[L[c]]=c;
    }
    bool dfs(int step)
    {
        if(step>=N) return true;
        if(R[0]==0) return false;
        int Min=INF,c=-1;
        for(int i=R[0];i;i=R[i])
            if(Min>S[i]){ Min=S[i]; c=i; }
        Remove(c);
        for(int i=D[c];i!=c;i=D[i])
        {
            //ans[step]=loc[i][0];
            for(int j=R[i];j!=i;j=R[j]) Remove(C[j]);
            if(dfs(step+1)) return true;
            for(int j=L[i];j!=i;j=L[j]) Resume(C[j]);
        }
        Resume(c);
        return false;
    }
}dlx;
View Code

spoj1771(N皇后精确覆盖DLX)

技术分享
题意:N皇后问题,但是棋盘已经有一些皇后,然后问如何选择皇后的位置使得他们互不攻击,N<=50

 

解析:N这么大,显然不可能爆搜,这里用到的是精确覆盖DLX算法,行表示每个点,DLX的列对应棋盘的行,列和两个方向的斜对角。

搜到N步就可以了,考虑行就好了,不用考虑斜对角什么的。

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
const int INF=1e9+7;
const int ms=51*51;
const int maxn=ms*5;
int N,ans[55],res[55];//ans存储第几个选择的编号,res保存第几行答案是第几列
struct DLX
{
    int n,id;
    int L[maxn],R[maxn],U[maxn],D[maxn];
    int C[maxn],S[maxn],loc[maxn][2];
    int H[ms];
    void init(int nn=0) //传列长
    {
        n=nn;
        for(int i=0;i<=n;i++) U[i]=D[i]=i,L[i]=i-1,R[i]=i+1;
        L[0]=n; R[n]=0;
        id=n;
        memset(S,0,sizeof(S));
        memset(H,-1,sizeof(H));
    }
    void Link(int x,int y)
    {
        ++id;
        D[id]=y; U[id]=U[y];
        D[U[y]]=id; U[y]=id;
        loc[id][0]=x,loc[id][1]=y;
        C[id]=y; S[y]++;
        if(H[x]==-1) H[x]=L[id]=R[id]=id;
        else
        {
            int a=H[x];
            int b=R[a];
            L[id]=a; R[a]=id;
            R[id]=b; L[b]=id;
            H[x]=id;
        }
    }
    void Remove(int c)
    {
        L[R[c]]=L[c];
        R[L[c]]=R[c];
        for(int i=D[c];i!=c;i=D[i])
            for(int j=R[i];j!=i;j=R[j])
        {
            U[D[j]]=U[j];
            D[U[j]]=D[j];
            S[C[j]]--;
        }
    }
    void Resume(int c)
    {
        for(int i=U[c];i!=c;i=U[i])
            for(int j=R[i];j!=i;j=R[j])
        {
            S[C[j]]++;
            U[D[j]]=j;
            D[U[j]]=j;
        }
        L[R[c]]=c;
        R[L[c]]=c;
    }
    bool dfs(int step)
    {
        if(step>=N) return true;
        if(R[0]==0) return false;
        int Min=INF,c=-1;
        for(int i=R[0];i;i=R[i])
        {
            if(i>N) break;
            if(Min>S[i]){ Min=S[i]; c=i; }
        }
        if(c==-1) return false;
        Remove(c);
        for(int i=D[c];i!=c;i=D[i])
        {
            ans[step]=loc[i][0];
            for(int j=R[i];j!=i;j=R[j]) Remove(C[j]);
            if(dfs(step+1)) return true;
            for(int j=L[i];j!=i;j=L[j]) Resume(C[j]);
        }
        Resume(c);
        return false;
    }
}dlx;
bool vis[55*6];
int main()
{
    while(scanf("%d",&N)!=EOF)
    {
        dlx.init(N*6-2);
        memset(vis,false,sizeof(vis));
        int y;
        for(int x=1;x<=N;x++)
        {
            scanf("%d",&y);
            if(y==0) continue;
            int a=x,b=N+y,c=2*N+N+x-y,d=4*N+x+y-2; //对应的行列斜对角编号
            vis[a]=vis[b]=vis[c]=vis[d]=true; //标记
            int t=(x-1)*N+y-1;
            dlx.Link(t,a); //连接
            dlx.Link(t,b);
            dlx.Link(t,c);
            dlx.Link(t,d);
        }
        for(int x=1;x<=N;x++)
            for(int y=1;y<=N;y++)
        {
            int a=x,b=N+y,c=2*N+N+x-y,d=4*N+x+y-2;
            if(vis[a]||vis[b]||vis[c]||vis[d]) continue; //有被占据不考虑
            int t=(x-1)*N+y-1;
            dlx.Link(t,a);
            dlx.Link(t,b);
            dlx.Link(t,c);
            dlx.Link(t,d);
        }
        if(!dlx.dfs(0)) printf("No answer find\n");
        else
        {
            for(int i=0;i<N;i++) res[ans[i]/N]=ans[i]%N;
            for(int i=0;i<N;i++) printf("%d%c",res[i]+1,i==N-1?\n: );
        }
    }
    return 0;
}
View Code

poj3074(数独问题精确覆盖DLX)

技术分享
题意: 给出一个9*9的矩阵,有一些格子已经填了数,有一些是.代表未填。求任意一组解使得每行包含1~9,每列包含1~9,每个小矩形(3*3)包含1~9。

 

解析: 精确覆盖DLX的经典题目,每一行代表要填数的情况,列共有81*4行,第一个81行代表第i行j列放了数,第二个81列代表第i行放的数k,第三个81列

代表第j列放得数k,第四个81行代表第i个小矩形放的数k。对于字符为.的情况添加9行,对于字符为数字的情况添加一行。然后就是跑一边DLX,保存一下答案

输出即可。

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
const int INF=1e9+7;
const int ms=81*10;
const int maxn=ms*4;
int ans[maxn];
struct DLX
{
    int n,id;
    int L[maxn],R[maxn],U[maxn],D[maxn];
    int C[maxn],S[maxn],loc[maxn][3];
    int H[ms];
    void init(int nn=0)
    {
        n=nn;
        for(int i=0;i<=n;i++) U[i]=D[i]=i,L[i]=i-1,R[i]=i+1;
        L[0]=n; R[n]=0;
        id=n;
        memset(S,0,sizeof(S));
        memset(H,-1,sizeof(H));
    }
    void Link(int x,int y,int px,int py,int k)
    {
        ++id;
        D[id]=y; U[id]=U[y];
        D[U[y]]=id; U[y]=id;
        loc[id][0]=px,loc[id][1]=py,loc[id][2]=k;
        C[id]=y; S[y]++;
        if(H[x]==-1) H[x]=L[id]=R[id]=id;
        else
        {
            int a=H[x];
            int b=R[a];
            L[id]=a; R[a]=id;
            R[id]=b; L[b]=id;
            H[x]=id;
        }
    }
    void Remove(int c)
    {
        L[R[c]]=L[c];
        R[L[c]]=R[c];
        for(int i=D[c];i!=c;i=D[i])
            for(int j=R[i];j!=i;j=R[j])
        {
            U[D[j]]=U[j];
            D[U[j]]=D[j];
            S[C[j]]--;
        }
    }
    void Resume(int c)
    {
        for(int i=U[c];i!=c;i=U[i])
            for(int j=R[i];j!=i;j=R[j])
        {
            S[C[j]]++;
            U[D[j]]=j;
            D[U[j]]=j;
        }
        L[R[c]]=c;
        R[L[c]]=c;
    }
    bool dfs(int step)
    {
        if(step==81) return true;
        if(R[0]==0) return false;
        int Min=INF,c=-1;
        for(int i=R[0];i;i=R[i])
            if(Min>S[i]){ Min=S[i]; c=i; }
        Remove(c);
        for(int i=D[c];i!=c;i=D[i])
        {
            ans[step]=i;
            for(int j=R[i];j!=i;j=R[j]) Remove(C[j]);
            if(dfs(step+1)) return true;
            for(int j=L[i];j!=i;j=L[j]) Resume(C[j]);
        }
        Resume(c);
        return false;
    }
}dlx;
int main()
{
    char S[90];
    while(scanf("%s",S)!=EOF)
    {
        if(S[0]==e) break;
        dlx.init(81*4);
        int k=0,r=0;
        for(int x=0;x<9;x++)
            for(int y=0;y<9;y++)
        {
            char ch=S[k++];
            int a,b,c,d;
            if(ch==.)
            {
                for(int i=1;i<=9;i++)
                {
                    a=x*9+y+1;
                    b=x*9+i+81;
                    c=y*9+i+81+81;
                    int s=(x/3)*3+y/3;
                    d=s*9+i+81+81+81;
                    ++r;
                    dlx.Link(r,a,x,y,i);
                    dlx.Link(r,b,x,y,i);
                    dlx.Link(r,c,x,y,i);
                    dlx.Link(r,d,x,y,i);
                }
            }
            else
            {
                int i=ch-0;
                a=x*9+y+1;
                b=x*9+i+81;
                c=y*9+i+81+81;
                int s=(x/3)*3+y/3;
                d=s*9+i+81+81+81;
                ++r;
                dlx.Link(r,a,x,y,i);
                dlx.Link(r,b,x,y,i);
                dlx.Link(r,c,x,y,i);
                dlx.Link(r,d,x,y,i);
            }
        }
        dlx.dfs(0);
        int res[10][10];
        for(int i=0;i<81;i++)
        {
            int a=ans[i];
            int x=dlx.loc[a][0],y=dlx.loc[a][1],k=dlx.loc[a][2];
            res[x][y]=k;
        }
        for(int i=0;i<9;i++)
            for(int j=0;j<9;j++) printf("%d",res[i][j]);
        printf("\n");
    }
    return 0;
}
View Code

 

图论

hdu5772(最大权闭合子图问题,网络流)

技术分享
解析:

多校标答

第一类:Pij 表示第i个点和第j个点组合的点,那么Pij的权值等于w[i][j]+w[j][i](表示得分)
第二类:原串中的n个点每个点拆出一个点,第i个点权值为 –a[s[i]] (表示要花费)
第三类:对于10种字符拆出10个点,每个点的权值为  -(b[x]-a[x])

 

最大权闭合图用网络流求解,根据原图建立一个等价的网络,构建规则如下。

对于点权为正的节点,从源点连一条容量为点权的边到该点,对于点权为负的边,从该点连一条容量为点权绝对值的边到汇点。原图中的边保留,容量为inf。最大权值即为图中所有正点权之和减去最大流。

这题我看了半天才理解。。。。。

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int INF=1e9+7;
const int maxn=102;
const int maxnode=maxn*maxn;
int N,a[10],b[10],w[maxn][maxn];
int eid,S,T; //eid用于边的编号,S,T分别为源点汇点
vector<int> G[maxnode]; //存储边的编号
struct edge
{
    int u,v,cap,flow; //头,尾,容量和流量
    edge(int u=0,int v=0,int cap=0,int flow=0):u(u),v(v),cap(cap),flow(flow){}
}E[maxnode*10];
void AddEdge(int u,int v,int c) //建边
{
    E[eid]=edge(u,v,c,0);  //正向边
    G[u].push_back(eid); eid++;
    E[eid]=edge(v,u,0,0);  //反向边
    G[v].push_back(eid); eid++;
}
bool vis[maxnode];
int d[maxnode],cur[maxnode];
queue<int> que;
bool BFS()
{
    memset(vis,false,sizeof(vis));
    while(!que.empty()) que.pop();
    vis[S]=true;
    d[S]=0;
    que.push(S);
    while(!que.empty())
    {
        int u=que.front();  que.pop();
        int Size=G[u].size();
        for(int i=0;i<Size;i++)
        {
            int id=G[u][i];
            edge& e=E[id];
            int v=e.v;
            if(!vis[v]&&e.cap>e.flow)
            {
                vis[v]=true;
                d[v]=d[u]+1;
                que.push(v);
            }
        }
    }
    return vis[T];
}
int DFS(int u,int a) //分层
{
    if(u==T||a==0) return a;
    int ret=0,f;
    int Size=G[u].size();
    for(int& i=cur[u];i<Size;i++)
    {
        int id=G[u][i];
        edge& e=E[id];
        int v=e.v;
        if(d[u]+1==d[v]&&(f=DFS(v,min(a,e.cap-e.flow)))>0)
        {
            ret+=f;
            e.flow+=f;
            E[id^1].flow-=f;
            a-=f;
            if(a==0) break;
        }
    }
    return ret;
}
int MaxFlow(int s,int t) //最大流Dinic算法
{
    S=s; T=t;
    int ret=0;
    while(BFS())
    {
        memset(cur,false,sizeof(cur));
        ret+=DFS(S,INF);
    }
    return ret;
}
int main()
{
    int TT,Case=0;
    scanf("%d",&TT);
    while(TT--)
    {
        char SS[maxn];
        scanf("%d",&N);
        scanf("%s",SS);
        for(int i=0;i<10;i++) scanf("%d%d",&a[i],&b[i]);
        int be=N*N+N+10;
        int en=be+1;
        for(int i=0;i<=en;i++) G[i].clear(); eid=0;
        int ans=0;
        for(int i=0;i<N;i++)
            for(int j=0;j<N;j++)
        {
            scanf("%d",&w[i][j]);
            if(i==j) continue;
            ans+=w[i][j];
            AddEdge(be,i*N+j,w[i][j]); 
            AddEdge(i*N+j,N*N+i,INF); 
            AddEdge(i*N+j,N*N+j,INF);
        }
        for(int i=0;i<N;i++) AddEdge(N*N+i,N*N+N+SS[i]-0,INF);
        for(int i=0;i<N;i++) AddEdge(N*N+i,en,a[SS[i]-0]);
        for(int i=0;i<10;i++) AddEdge(N*N+N+i,en,b[i]-a[i]);
        printf("Case #%d: %d\n",++Case,ans-MaxFlow(be,en));
    }
    return 0;
}
View Code

zoj3332(有向竞赛图)

技术分享
解析: 裸的有向竞赛图,有向竞赛图要满足的条件是有N*(N-1)/2条边,也就是每个点与其他点都有边相连,求哈密顿通路。

代码
#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<utility>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<iterator>
#include<stack>
using namespace std;
const int maxn=105;
int N,ans[maxn];
bool link[maxn][maxn];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&N);
        memset(link,false,sizeof(link));
        int tot=N*(N-1)/2;
        int u,v;
        for(int i=1;i<=tot;i++)
        {
            scanf("%d%d",&u,&v);
            link[u][v]=true;
        }
        ans[1]=1;
        int i,j,k;
        for(k=2;k<=N;k++)
        {
            for(j=1;j<k;j++) if(link[k][ans[j]]) break;
            for(i=k;i>j;i--) ans[i]=ans[i-1];
            ans[j]=k;
        }
        for(int i=1;i<=N;i++) printf("%d%c",ans[i],i==N?\n: );
    }
    return 0;
}
View Code

uva11183(有向图最小生成树朱刘算法)

技术分享
解析: 裸的有向图最小生成树

#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<utility>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<iterator>
#include<stack>
using namespace std;
const int INF=1e9+7;
const int eps=0.0000001;
const int maxn=1005;
struct edge
{
    int u,v,w;
    edge(int u=0,int v=0,int w=0):u(u),v(v),w(w){}
}E[40005];
int pre[maxn],InEdge[maxn],vis[maxn],id[maxn];
int Dir_MST(int root,int Vcnt,int Ecnt)
{
    int ret=0;
    while(true)
    {
        for(int i=1;i<=Vcnt;i++) InEdge[i]=INF;
        for(int i=1;i<=Ecnt;i++)
        {
            edge& e=E[i];
            int u=e.u,v=e.v,w=e.w;
            if(u==v) continue;
            if(w<InEdge[v]) { InEdge[v]=w; pre[v]=u; } //找最小的指向v的边
        }
        InEdge[root]=0;
        for(int i=1;i<=Vcnt;i++) if(i!=root&&InEdge[i]==INF) return -1;//存在某个点跟整个图分离
        int ID=0;
        for(int i=1;i<=Vcnt;i++) vis[i]=id[i]=-1;
        for(int i=1;i<=Vcnt;i++)
        {
            ret+=InEdge[i];  //把那些边加进答案
            int a=i;
            while(vis[a]!=i&&id[a]==-1&&a!=root){ vis[a]=i; a=pre[a]; }
            if(id[a]==-1&&a!=root)
            {
                ++ID;
                for(int b=pre[a];b!=a;b=pre[b]) id[b]=ID;  //重新编号
                id[a]=ID;
            }
        }
        if(ID==0) return ret;  //找到解
        for(int i=1;i<=Vcnt;i++) if(id[i]==-1) id[i]=++ID; //独立的点编号
        for(int i=1;i<=Ecnt;i++)
        {
            edge& e=E[i];
            int u=e.u,v=e.v;
            e.u=id[u];
            e.v=id[v];
            if(id[u]!=id[v]) e.w-=InEdge[v]; //之前加的那一部分要减掉
        }
        Vcnt=ID;
        root=id[root];
    }
}
int main()
{
    int T,Case=0;
    scanf("%d",&T);
    while(T--)
    {
        int N,M,u,v,w;
        scanf("%d%d",&N,&M);
        for(int i=1;i<=M;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            u++; v++;
            E[i]=edge(u,v,w);
        }
        int ans=Dir_MST(1,N,M);
        printf("Case #%d: ",++Case);
        if(ans==-1) printf("Possums!\n");
        else printf("%d\n",ans);
    }
    return 0;
}
View Code

强连通分量

技术分享
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
const int maxn=10005;
vector<int> G[maxn];
int N,M,ans; 
bool inq[maxn];  //是否在栈内
int nid,dfn[maxn],low[maxn];  //dfn相当于重新编号,low能搜到的最小编号
stack<int> KK;  //
void init()  //初始化
{
    ans=nid=0;
    for(int i=0;i<=N;i++)
    {
        inq[i]=false;
        dfn[i]=low[i]=0;
    }
    while(!KK.empty()) KK.pop();
}
void Tarjan(int u)
{
    dfn[u]=low[u]=++nid;  //编号
    KK.push(u); inq[u]=true; //丢进栈里
    int Size=G[u].size();
    for(int i=0;i<Size;i++)
    {
        int v=G[u][i];
        if(!dfn[v])  //没有访问过
        {
            Tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(inq[v]) low[u]=min(low[u],dfn[v]); //访问过在栈里
    }
    if(dfn[u]==low[u])//相等说明这是一个连通分量
    {
        ans++;
        int v;
        do
        {
            v=KK.top();  KK.pop();
            inq[v]=false;
        }while(v!=u);
    }
}
int main()
{
    while(scanf("%d%d",&N,&M)!=EOF)
    {
        if(!N&&!M) break;
        for(int i=0;i<=N;i++) G[i].clear();
        int u,v;
        while(M--)
        {
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
        }
        init();
        for(int i=1;i<=N;i++) if(!dfn[i]) Tarjan(i);
        if(ans<=1) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}
View Code

hdu3072(强连通求最小值)

技术分享
 题意:简单点说就是求把所有强连通分量连在一起所需的最小花费

 解析:先把所有强连通分量求出来,再求不同连通分量连接起来的最小花费,最后把除0所在的连通分量所需的最小花费连接起来,

#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<utility>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<iterator>
#include<stack>
using namespace std;
const int INF=1e9+7;
const int eps=0.0000001;
typedef __int64 LL;
const int maxn=50005;
const int maxm=100005;
int N,M,ans,id;
int dfn[maxn],low[maxn],cost[maxn],resign[maxn];
vector<int> G[maxn];
stack<int> KK;
bool inq[maxn];
struct edge
{
    int u,v,w;
    edge(int u=0,int v=0,int w=0):u(u),v(v),w(w){}
}E[maxm];
void init()
{
    ans=id=0;
    while(!KK.empty())  KK.pop();
    for(int i=0;i<=N;i++)
    {
        dfn[i]=low[i]=0;
        resign[i]=0;
        cost[i]=INF;
        G[i].clear();
        inq[i]=false;
    }
}
void Tarjan(int x)
{
    dfn[x]=low[x]=++id;
    inq[x]=true;
    KK.push(x);
    int t,Size=G[x].size();
    for(int i=0;i<Size;i++)
    {
        t=G[x][i];
        if(!dfn[t])
        {
            Tarjan(t);
            low[x]=min(low[x],low[t]);
        }
        else if(inq[t]) low[x]=min(low[x],dfn[t]);
    }  //前面都差不多
    if(dfn[x]==low[x])
    {
        ans++;
        do
        {
            t=KK.top(); KK.pop();
            inq[t]=false;
            resign[t]=ans;  //这个地方,标记连通分量
        }while(t!=x);
    }
    return;
}
int main()
{
    while(scanf("%d%d",&N,&M)!=EOF)
    {
        init();
        int u,v,w;
        for(int i=1;i<=M;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            E[i]=edge(u,v,w);
            G[u].push_back(v); //有向图
        }
        for(int i=0;i<N;i++) if(!dfn[i]) Tarjan(i);
        for(int i=1;i<=M;i++)
        {
            edge& e=E[i];
            int u=e.u,v=e.v,w=e.w;
            int x=resign[u],y=resign[v]; //连通分量繁荣编号 
            if(x!=y) cost[y]=min(cost[y],w);  //更新值
        }
        int sum=0;
        for(int i=1;i<=ans;i++)
        {
            if(i==resign[0]||cost[i]==INF) continue;//跟0是统一连通分量的不管
            sum+=cost[i];
        }
        printf("%d\n",sum);
    }
    return 0;
}
View Code

hdu4005(双连通缩点)

技术分享
题意: 敌人在N个点间建了M条线路,每条线路都有一个权值,敌人要再建一条线路,己方可以毁掉敌方的一条线路,问己方最少要花多少钱(至少得准备多少钱)才能使

这些点不连通。

 

解析: 先分离出每个双连通分量(删除双联通分量中的任何一条边还是连通的,没有意义),给每个分量重新编一个号,缩成一个点,在Tarjan算法过程中把桥保存下来。

选一条权值最小的桥,分别从两头出发,找次小的桥。答案就是次小的桥的权值,为甚么是找次小的桥,因为如果敌人是把最小的桥所连的两个连通分量再加一条边,则

必须删次小的桥,如果是其他,则只需要删最小的桥即可。

#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<utility>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<iterator>
#include<stack>
using namespace std;
typedef __int64 LL;
const int INF=1e9+7;
const double eps=1e-7;
const int maxn=10005;
const int maxm=100005;
int N,M,eid,id,top;
int scc_id,scc[maxn],dfn[maxn],low[maxn],b[maxn],fa[maxn];
int head[maxn],KK[maxn];
struct edge
{
    int v,w,next;
    edge(int v=0,int w=0,int next=-1):v(v),w(w),next(next){}
}E[2*maxm];
struct node
{
    int u,v,w;
    node(int u=0,int v=0,int w=0):u(u),v(v),w(w){}
};
vector<node> V;
void init()
{
    eid=id=top=scc_id=0;
    for(int i=0;i<=N;i++)
    {
        head[i]=-1;
        dfn[i]=low[i]=b[i]=scc[i]=0;
        fa[i]=i;
    }
    V.clear();
}
void AddEdge(int u,int v,int w)
{
    E[++eid]=edge(v,w,head[u]);
    head[u]=eid;
}
void Tarjan(int u)
{
    dfn[u]=low[u]=++id;
    KK[top++]=u;
    bool first=false;
    for(int i=head[u];i!=-1;i=E[i].next)
    {
        edge& e=E[i];
        int v=e.v,w=e.w;
        if(v==fa[u]&&!first){ first=true; continue; }
        if(!dfn[v])
        {
            fa[v]=u;
            Tarjan(v);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u]) V.push_back(node(u,v,w));//这条边是桥
        }
        else low[u]=min(low[u],dfn[v]);
    }
    int t;
    if(dfn[u]==low[u])
    {
        scc_id++;  //双连通分量
        do
        {
            t=KK[--top];
            b[t]=scc_id; //连通分量编号
        }while(u!=t);
    }
    return;
}
int ans;
int FindPath(int u,int pre)
{
    int Min=INF,MMin=INF;
    for(int i=head[u];i!=-1;i=E[i].next)
    {
        int v=E[i].v,w=E[i].w;
        if(v==pre) continue;
        int t=FindPath(v,u);
        if(t<MMin) MMin=t;
        if(w<MMin) MMin=w;
        if(Min>MMin) swap(Min,MMin);
    }
    ans=min(ans,MMin);
    return Min;
}
int main()
{
    while(scanf("%d%d",&N,&M)!=EOF)
    {
        init();
        int u,v,w;
        for(int i=1;i<=M;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            AddEdge(u,v,w); //建边
            AddEdge(v,u,w); //反向边
        }
        for(int i=1;i<=N;i++) if(!dfn[i]) Tarjan(i); //找连通分量

        int mindist=INF,picku,pickv,Size=V.size();
        eid=0;
        memset(head,-1,sizeof(head));
        for(int i=0;i<Size;i++) //重新建图
        {
            node& t=V[i];
            int u=t.u,v=t.v,w=t.w;
            AddEdge(b[u],b[v],w);
            AddEdge(b[v],b[u],w);
            if(w<mindist){ mindist=w,picku=b[u],pickv=b[v]; } //权值最小的桥
        }
        ans=INF;
        FindPath(picku,pickv);
        FindPath(pickv,picku);
        if(ans==INF) printf("-1\n");
        else printf("%d\n",ans);
    }
    return 0;
}
View Code

 

 

 

 

2016wust暑假集训之题解汇总2

标签:

原文地址:http://www.cnblogs.com/wustacm/p/5754362.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!