HNOI2018省队集训 Day4&5
Day4很毒,先鸽了(HN队长都鸽了,所以不能怪我)
marshland
前方有一片沼泽地.
方便地, 我们用一个 n × n 的网格图来描述它, 每一个格子代表着沼泽地 的一小片区域. 其中 (1, 1) 代表网格图的左上角, (n, n) 代表网格图的右下角. 若用 X 表示行数, Y 表示列数, 那么 X + Y 为奇数的格子有一个危险度 VX,Y , X + Y 为偶数的格子的危险度为 0.
为了保障人们的安全, 你有 m 个长相怪异的大石头, 你可以选一些石头 放在网格图上的某些格子上, 石头可以看成一个 ‘L’ 形的块, 并且占三个格子, 它通过旋转有四种方式供放置, 仅会使得在拐角处的那个格子危险度减为 0.
网格图中还有 k 个位置是 “禁止位置”, 石头的任何部位都不能位于这些 格子上, 且这些位置的危险度一定为 0.
现在你需要知道放置一些石头后最小的危险度之和是多少. (石头可以不放完)
考虑费用流,最开始我考虑的是把选择每个石头抽象成点,有个常见的建法:
这样4、3里面只能选一个,3、6里面只能选一个,并且436每个最多选一次,貌似很符合这道题。但是这道题的每个限制是选了一个其他的无法再选,显然这样的模型无法保证(1-5-6,1-2-3)。
那么考虑对于每个点只能选一次这个限制和每个点属于哪个L形,容易发现L形可以被拆分为三个点:奇数列的偶数行点,奇数列的奇数行点,偶数列的偶数行点。这么做我们就只需要用上面的模型的后半部分(每个点被选一次),建立三排点。而且由于这些点行数和列数的奇偶性不一样,这样做就是对的。
关于计算答案,只需要把中间的点拆开加上一条(-w,1)的边即可。
注意跑费用流的时候不能dfs多路增广,需要找一条增广路增广,保证最大流每次+1,流到m或者答案不再减少直接退出即可。
party
显然party会在LCA出召开,每个点选的范围就是当前点到LCA的路径。
那么我们可以用(O(nfrac m omega))树剖+线段树建立,(O(qclog nfrac m omega))查询每个询问中每个点可选集合。
对于每个询问怎么求答案呢?考虑到每个权值只能被选一次,题解告诉我们可以用二分图+Hall定理做。
Hall定理:二分图G存在完美匹配当且仅当X中的任意k个点至少与Y中的k个点相邻
我们可以(2^cfrac m omega)求出当前点的集合可选权值种类的并集(f(S)) (注意这里可以递推来减少常数),然后对于每个集合(|S|)考虑k。因为答案一定是(c*x)的形式,就可以构造一个左边(|S|*x)个点,右边(f(S))个点的二分图。 首先对于(k=|S|*x),(x)最大为 (frac{f(S)}{|S|}),因为由于(f(S))的定义左边的点和右边一定是完全联通的,只要总数(|S|*xleq f(S))即可。然后对于其他的至少含有 这个集合的每个点的 左边的x个点中的一个点 的k值的条件一定可以通过构造得到(去掉一些点)。那么总的x的最大值就是满足每个条件的最大值的最小值。
总复杂度(O(nfrac m w+qclog nfrac m w+q2^cfrac m w))
最开始写T了,因为没有预处理到链顶的答案。这在没有修改的题好像是常规操作,然而我一直用的是log^2...
platform
切了...但是要注意本质不同的子串算不算,我以为是算的,结果WA了一发。要仔细读题和样例解释!
考虑左端点相同的子串,右端点增大,权值和不减,降序排名单增(前面的串是后面的前缀)。那么2-1仍然单增,二分找到差值(leq 0)的最后一个,判断是否等于0(相等)即可。
快速找子串排名我用的是反串的SAM的parent树上倍增+预处理出这个节点取到len最大的排名。具体加边方法画个SAM就知道了。总复杂度(nlog^2n),勉强卡过。
然后题解前半部分跟我是一样的,后半部分SA能做到(log n),是用线段树维护区间赋值和加等差数列,SAM就暴力许多。
贴个代码吧:
#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;++i)
#define ROF(i,a,b) for(int i=a;i>=b;--i)
#define ll long long
using namespace std;
const int N = 4e5+200;
int read(){
int x=0,pos=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return pos?x:-x;
}
char s[N];int n,w[N],pos[N],f[N][20];
ll val[N],vs[N],rk[N];
int las=1,tot=1;
struct typ{
int ch[26],fa,len,pos;
typ(){
memset(ch,0,sizeof(ch));fa=len=0;
}
}t[N];
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
vector<pii> edge[N];
void insert(int c,int id){
int p=las,np=las=++tot;pos[id]=np;w[np]=1;t[np].pos=id;
t[np].len=t[p].len+1;
for(;p&&!t[p].ch[c];p=t[p].fa) t[p].ch[c]=np;
if(!p){
t[np].fa=1;
}else{
int q=t[p].ch[c];
if(t[p].len+1==t[q].len) t[np].fa=q;
else{
int nq=++tot;
t[nq]=t[q];
t[nq].len=t[p].len+1,t[q].fa=t[np].fa=nq;
for(;t[p].ch[c]==q;p=t[p].fa) t[p].ch[c]=nq;
}
}
}
int ti[N],lnk[N];
ll rn=0;
void dfs1(int now){
for(int i=0;i<edge[now].size();i++){
int v=edge[now][i].se;
dfs1(v),w[now]+=w[v];if(!t[i].pos) t[i].pos=t[v].pos;
}
}
int cmp(pii a,pii b){
return a.fi>b.fi;
}
void dfs(int now){
sort(edge[now].begin(),edge[now].end(),cmp);
for(int i=0;i<edge[now].size();i++){
int v=edge[now][i].se;
dfs(v);
}
rk[now]=rn;
rn+=1ll*(t[now].len-t[t[now].fa].len);//*w[now];
}
ll calcrk(int l,int r){
int len=r-l+1,x=pos[l];
ROF(i,19,0){
if(t[f[x][i]].len>=len) x=f[x][i];
}
return 1ll+rk[x]+1ll*(t[x].len-len);//*w[x];
}
ll calcv(int l,int r){
return vs[r]-vs[l-1];
}
ll calc(int l,int r){
return calcv(l,r)-calcrk(l,r);
}
vector<pii> ans;
int main(){
scanf("%s",s+1);
n=strlen(s+1);
FOR(i,1,n){
val[i]=read();vs[i]=vs[i-1]+val[i];
}
ROF(i,n,1){
insert(s[i]-'a',i);
}
FOR(i,2,tot){
f[i][0]=t[i].fa;
edge[t[i].fa].push_back({0,i});
}
dfs1(1);
FOR(i,1,tot) edge[i].clear();
FOR(i,2,tot){
edge[t[i].fa].push_back({s[t[i].pos+t[t[i].fa].len]-'a',i});
}
dfs(1);
FOR(i,1,19){
FOR(j,1,tot){
f[j][i]=f[f[j][i-1]][i-1];
}
}
FOR(i,1,n){
int l=i,r=n+1;
while(l<r-1){
int mid=(l+r)>>1;
if(calc(i,mid)<=0) l=mid;
else r=mid;
}
if(calc(i,l)==0){
ans.push_back({i,l});
}
}
printf("%d
",ans.size());
if(!ans.size()) return 0;
FOR(i,0,ans.size()-1){
printf("%d %d
",ans[i].fi,ans[i].se);
}
return 0;
}