[2012年四川省选]简要题解

来源:互联网 发布:微店和淘宝哪个好做 编辑:程序博客网 时间:2024/04/28 17:54

奇怪的游戏

http://www.lydsy.com/JudgeOnline/problem.php?id=2756

题解

似乎这个题我做过?
这是一道非常不错的黑白染色的最大流建模题。。。
首先按照惯例对整个棋盘进行黑白染色,不妨设sum1=黑色格子的数字之和,num1=黑色格子的个数,sum2=白色格子的数字之和,num2=白色格子的个数,最终所有格子的数字均变成了x,可以列出下面的等式:
num1xsum1=num2xsum2
得到x=sum1sum2num1num2(num1!=num2)
因此在num1!=num2时,我们可以快速地得到x。而在num1=num2时,上面的等式就废了,只能二分x

下面问题变成了已知x的值,判断x是否是合法解。

这就是一个黑白染色的最大流模型,我们让源点S连向所有黑色格子,每条边的容量是x,所有白色格子连向汇点T,每条边的容量是x,显然S发出的边容量之和即为黑色格子可以增加的数字之和,T收到的边容量之和即为白色格子可以增加的数字之和,然后所有黑色格子向它们相邻的白色格子连容量无穷大的边,表示可以同时选择这两个格子,同时增加任意大小的数字。显然这个图满流的话x就合法,否则x不合法,用Dinic跑一遍就OK了。

另外注意Dinic的一些优化,不优化会TLE。

代码

#include <iostream>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <algorithm>#define MAXE 21000#define MAXV 2100#define INF 0x3f3f3f3f3f3f3f3fusing namespace std;typedef long long int LL;int n,m;int S,T;struct edge{    LL cap;    int u,v,next;}edges[MAXE];int head[MAXV],nCount=1;inline void AddEdge(int U,int V,LL C){    edges[++nCount].u=U;    edges[nCount].v=V;    edges[nCount].cap=C;    edges[nCount].next=head[U];    head[U]=nCount;}inline void add(int U,int V,LL C){    AddEdge(U,V,C);    AddEdge(V,U,0);}int q[MAXE];int layer[MAXV];inline bool CountLayer(){    memset(layer,-1,sizeof(layer));    int h=0,t=1;    q[h]=S;    layer[S]=0;    while(h<t)    {        int u=q[h++];        for(int p=head[u];p!=-1;p=edges[p].next)        {            int v=edges[p].v;            if(edges[p].cap&&layer[v]==-1)            {                layer[v]=layer[u]+1;                q[t++]=v;            }        }    }    return layer[T]!=-1;}LL DFS(int u,LL flow){    if(u==T) return flow; //!!!!!    LL used=0;    for(int p=head[u];p!=-1;p=edges[p].next)    {        int v=edges[p].v;        if(layer[v]==layer[u]+1)        {            LL tmp=DFS(v,min(flow-used,edges[p].cap));            used+=tmp;            edges[p].cap-=tmp;            edges[p^1].cap+=tmp;            if(used==flow) return flow;        }    }    if(!used) layer[u]=-1;    return used;}inline LL Dinic(){    LL maxflow=0;    while(CountLayer())        maxflow+=DFS(S,INF);    return maxflow;}int a[MAXV][MAXV];int color[MAXV][MAXV];inline int calc(int x,int y){    return (x-1)*m+y;}int xx[]={1,-1,0,0},yy[]={0,0,1,-1};inline bool check(LL x) //检查所有格子都变成数字x是否可能{    S=MAXV-2,T=MAXV-1;    nCount=1;    memset(head,-1,sizeof(head));    LL sum=0; //!!!!满流的流量    for(int i=1;i<=n;i++)        for(int j=1;j<=m;j++)        {            if(color[i][j])            {                add(S,calc(i,j),x-a[i][j]);                sum+=x-a[i][j];                for(int dir=0;dir<4;dir++)                {                    int newi=i+xx[dir],newj=j+yy[dir];                    if(newi<1||newi>n||newj<1||newj>m) continue;                    add(calc(i,j),calc(newi,newj),INF);                }            }            else add(calc(i,j),T,x-a[i][j]);        }    return Dinic()==sum;}int main(){    int T;    scanf("%d",&T);    while(T--)    {        LL maxa=0,sum1=0,sum2=0,num1=0,num2=0;        scanf("%d%d",&n,&m);        for(int i=1;i<=n;i++)            for(int j=1;j<=m;j++)            {                scanf("%d",&a[i][j]);                color[i][j]=(i+j)%2;                maxa=max(maxa,(LL)a[i][j]);            }        for(int i=1;i<=n;i++)            for(int j=1;j<=m;j++)            {                if(color[i][j])                {                    sum1+=a[i][j];                    num1++;                }                else                {                    sum2+=a[i][j];                    num2++;                }            }        if(num1!=num2)        {            if((sum1-sum2)/(num1-num2)>=maxa)                if(check((sum1-sum2)/(num1-num2)))                {                    printf("%lld\n",((sum1-sum2)/(num1-num2))*num1-sum1);                    continue;                }            puts("-1");            continue;        }        LL ans,lowerBound=maxa,upperBound=1e18;        while(lowerBound<=upperBound)        {            LL mid=(lowerBound+upperBound)>>1;            if(check(mid))            {                ans=mid;                upperBound=mid-1;            }            else lowerBound=mid+1;        }        printf("%lld\n",ans*num1-sum1);    }    return 0;}

喵星球上的点名

http://www.lydsy.com/JudgeOnline/problem.php?id=2754

题解

来自出题人满满的恶意。。。。
有两种做法:1、AC自动机 2、后缀数组。
AC自动机做法很复杂,因为此题非常丧病地没有限定字符集的大小,这样就导致不能用Trie树传统的保存儿子的方式,只能用map,并且这样会让最终的算法复杂度多一个log

而后缀数组的做法就随意了很多,因为SA对字符集没有什么特别的要求,字符串都是可以看成是数字串,非常爽,果断用SA。

SA的做法看上去就是乱搞:首先把所有猫的名的字符串、猫的姓的字符串、点名的字符串拼成一个大的字符串,每个不同的串之间用不同的分割符隔开,注意要不同的分割符,为了避免RP爆零时匹配错误。

然后注意到题目的一个很重要的限制:一个猫被点名,点名串可以是它的姓的子串,也可是它的名的子串,但是绝对不能是它的姓和名拼起来的子串,因此每个猫的姓和名必须分开看待。

另外要注意到SA一个非常关键的性质:对于一些具有相同前缀的后缀来说,他们的排名是挨在一起的,对于一个开头下标为t,长为len的子串而言,包含它的后缀与开头下标为t的后缀的LCP值一定是大于等于len的,据此,对于每个点名串i,我们可以暴力在height数组中扫一遍找出一个区间[L,R],以这个区间中的数字isa[i]作为开头下标的后缀都是包含第i个点名串的,然后在[L,R]中扫一遍,通过sa数组看有哪些猫的名或姓在这其中,统计答案即可。

代码

#include <iostream>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <algorithm>#define MAXN 510000using namespace std;int wa[MAXN],wb[MAXN],cnt[MAXN],wv[MAXN];int rank[MAXN],height[MAXN],sa[MAXN];bool cmp(int *r,int a,int b,int c) //一个元素是(a,a+c),另一个元素是(b,b+c){    return (r[a]==r[b])&&(r[a+c]==r[b+c]);}void SA(int *r,int  n,int m) //待排序串为r,长为n(第n项为空,防止cmp函数溢出),后缀数组为sa(sa[i]=排名为i的后缀的开头下标),字母范围在[0,m){    int i,j,p;    int *x=wa,*y=wb; //其实就是滚动数组    for(i=0;i<m;i++) cnt[i]=0; //桶清零    for(i=0;i<n;i++) cnt[(x[i]=r[i])]++; //将每位字符放入对应的桶中    for(i=1;i<m;i++) cnt[i]+=cnt[i-1]; //此时cnt[i]=小于等于i的字符个数    for(i=n-1;i>=0;i--) sa[--cnt[x[i]]]=i;    for(j=1,p=1;p<n;j*=2,m=p) //处理每个后缀长度为j的前缀的排序,p是这些前缀的个数,m是每次基数排序的关键字最大值    {        //先按照第二关键字排一遍序        for(p=0,i=n-j;i<n;i++) y[p++]=i; //把第二关键字为0的那部分元素先放进来        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; //只有那些开头下标>=j的元素才能当作第二关键字,y[i]=第二关键字排名为i的元素的第一关键字下标        for(i=0;i<n;i++) wv[i]=x[y[i]];        for(i=0;i<m;i++) cnt[i]=0; //桶清零        for(i=0;i<n;i++) cnt[wv[i]]++;        for(i=1;i<m;i++) cnt[i]+=cnt[i-1]; //此时cnt[i]=小于等于i的字符个数        for(i=n-1;i>=0;i--) sa[--cnt[wv[i]]]=y[i]; //更新sa数组        swap(x,y); //交换x、y数组,计算新的x数组        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++; //更新名次数组x[],x[i]=开头下标为i的后缀的排名。若当前的后缀与之前的后缀排名相同,排名p不能+1,否则排名p要加1    }}void cal(int *r,int n) //此时的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++);}struct Query{    int l,start; //start=该点名串开始的字符的下标,l=该点名串长度}query[MAXN];int num[MAXN];int belong[MAXN]; //belong[i]=字符串中第i位所属的猫编号int ans[MAXN]; //ans[i]=第i只猫被点名次数int vis[MAXN]; //vis[i]=第i只猫所属的点名串编号,防止重复点名int main(){    int len=0;    int n,m;    scanf("%d%d",&n,&m);    int breaker=11000;    for(int i=1;i<=n;i++) //读入第i个猫的姓和名    {        int l;        scanf("%d",&l);        for(int j=1;j<=l;j++)        {            belong[len]=i;            scanf("%d",&num[len++]);        }        num[len++]=++breaker; //所有的分割符都必须完全不一样,避免错误匹配        scanf("%d",&l);        for(int j=1;j<=l;j++)        {            belong[len]=i;            scanf("%d",&num[len++]);        }        num[len++]=++breaker; //所有的分割符都必须完全不一样,避免错误匹配    }    for(int i=1;i<=m;i++)    {        scanf("%d",&query[i].l);        query[i].start=len;        for(int j=1;j<=query[i].l;j++)            scanf("%d",&num[len++]);        num[len++]=++breaker;    }    SA(num,len,200000);    cal(num,len-1);    for(int i=1;i<=m;i++)    {        int L,R;        L=R=rank[query[i].start];        while(height[L]>=query[i].l) L--; //区间[L,R]里排名对应的后缀都包含了第i个点名串,它们与以这个点名串的开头在整个拼起来的串的后缀的LCP值,肯定是大于等于这个点名串的长度的        while(height[R]>=query[i].l) R++;        R--;        int sum=0; //sum=第i次点名点到的猫的个数        for(int j=L;j<=R;j++) //枚举区间[L,R]里每个后缀的前缀对应的是哪些猫        {            if(belong[sa[j]]) //排名为j的后缀的开头是属于某个猫的            {                if(vis[belong[sa[j]]]!=i) //在第i个点名串的计数过程中还没有算入这只猫                {                    vis[belong[sa[j]]]=i;                    sum++;                    ans[belong[sa[j]]]++;                }            }        }        printf("%d\n",sum);    }    for(int i=1;i<=n;i++)        printf("%d%c",ans[i],i==n?'\n':' ');    return 0;}
0 0
原创粉丝点击