奇怪的游戏
http://www.lydsy.com/JudgeOnline/problem.php?id=2756
题解
似乎这个题我做过?
这是一道非常不错的黑白染色的最大流建模题。。。
首先按照惯例对整个棋盘进行黑白染色,不妨设
sum1=
黑色格子的数字之和,
num1=
黑色格子的个数,
sum2=
白色格子的数字之和,
num2=
白色格子的个数,最终所有格子的数字均变成了
x
,可以列出下面的等式:
得到
x=sum1−sum2num1−num2(num1!=num2)
因此在
num1!=num2
时,我们可以快速地得到
x
。而在
下面问题变成了已知
这就是一个黑白染色的最大流模型,我们让源点
另外注意Dinic的一些优化,不优化会TLE。
代码
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXE 21000
#define MAXV 2100
#define INF 0x3f3f3f3f3f3f3f3f
using 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
,长为
代码
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 510000
using 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;
}