T1:「雅礼集训 2018 Day11」进攻!
题目描述:
n
,
m
≤
2000
,
k
≤
1
0
6
n,m\le2000,k\le10^6
n,m≤2000,k≤106
LOJ link
题目分析:
求选K个全1矩形使其有交的方案数。
考虑容斥,计算每个
1
×
1
1\times 1
1×1的矩形被多少个矩形包含,设为
s
s
s,那么答案加上
s
k
s^k
sk
然后看多算了什么,假设某个方案中矩形的交的大小是一个
x
∗
y
x*y
x∗y的矩形,那么它被算了
x
∗
y
x*y
x∗y次。
减去
1
×
2
1\times 2
1×2的矩形,
2
×
1
2\times 1
2×1的矩形,加上
2
×
2
2\times 2
2×2的矩形。
那么一个交为
x
∗
y
x*y
x∗y的方案被算了
x
∗
y
−
x
(
y
−
1
)
−
y
(
x
−
1
)
+
(
x
−
1
)
(
y
−
1
)
=
1
x*y-x(y-1)-y(x-1)+(x-1)(y-1)=1
x∗y−x(y−1)−y(x−1)+(x−1)(y−1)=1 次。
原理可以理解为 点 - 边 + 环 = 1,矩形是一个天然的简单环结构。
那么问题就是计算 1 / 2 × 1 / 2 1/2\times 1/2 1/2×1/2 的矩形被多少个全1矩形包含。
先考虑
1
×
1
1\times 1
1×1
一个全1矩形会对内部的点有1的贡献,差分之后就是四个角+1或-1,但是矩形并不是输入的,不太好分开一个一个加。
求出以每个点为右下角的全1矩形个数,就可以算出右下角的+1的差分值;同理,求出以每个点为左上/左下/右下的全1矩形个数,就可以算出每个位置的差分值。最后求前缀和就可以得到每个点被覆盖的次数。
对于其它情况类似。求以每个点为右下角的全1矩形个数可以单调队列(延伸作限制,维护单调上升的立柱)
Code:
#include<bits/stdc++.h>
#define maxn 2005
#define rep(i,j,k) for(int i=(j),lim=(k);i<=lim;i++)
using namespace std;
const int mod = 998244353;
int n,m,K,s[maxn][maxn],f[maxn][maxn],d[maxn][maxn][4],ans;
char a[maxn][maxn];
int q[maxn],tp;
int Pow(int a,int b){int s=1;for(;b;b>>=1,a=1ll*a*a%mod) b&1&&(s=1ll*s*a%mod);return s;}
void solve(){
rep(i,1,n){
int res=0; tp=0;
rep(j,1,m){
s[i][j]=a[i][j]=='1'?s[i-1][j]+1:0;
for(;tp&&s[i][q[tp]]>=s[i][j];tp--) res-=(q[tp]-q[tp-1])*s[i][q[tp]];
res+=(j-q[tp])*s[i][j], q[++tp]=j;
f[i][j]=res;
}
}
}
int main()
{
//freopen("attack.in","r",stdin);
//freopen("attack.out","w",stdout);
scanf("%d%d%d",&n,&m,&K);
for(int i=1;i<=n;i++) scanf("%s",a[i]+1);
solve();
rep(i,1,n) rep(j,1,m) rep(k,0,3) d[i+1][j+1][k]=f[i][j];
rep(i,1,n) reverse(a[i]+1,a[i]+1+m);
solve();
rep(i,1,n) rep(j,1,m) rep(k,0,3) d[i+1][j+(k&1)][k]-=f[i][m-j+1];
reverse(a+1,a+1+n);
solve();
rep(i,1,n) rep(j,1,m) rep(k,0,3) d[i+(k>=2)][j+(k&1)][k]+=f[n-i+1][m-j+1];
rep(i,1,n) reverse(a[i]+1,a[i]+1+m);
solve();
rep(i,1,n) rep(j,1,m) rep(k,0,3) d[i+(k>=2)][j+1][k]-=f[n-i+1][j];
rep(i,1,n) rep(j,1,m) rep(k,0,3)
d[i][j][k]=(1ll*d[i][j][k]+d[i-1][j][k]+d[i][j-1][k]-d[i-1][j-1][k])%mod,
ans=(ans+(!k||k==3?1:-1)*Pow(d[i][j][k],K))%mod;
printf("%d\n",(ans+mod)%mod);
}
T2:「雅礼集训 2018 Day11」字符串
N , M ≤ 1 0 5 , ∑ ∣ s i ∣ ≤ 3 ∗ 1 0 5 N,M\le10^5,\sum|s_i|\le3*10^5 N,M≤105,∑∣si∣≤3∗105
题目分析:
莫队。初步尝试用 Trie树 + set 维护答案
直接对
n
n
n 莫队的话,可能每次都加入一个很长的串。
发现串总长
≤
3
∗
1
0
5
\le 3*10^5
≤3∗105,即每个串的前缀的个数
≤
3
∗
1
0
5
\le 3*10^5
≤3∗105
那么把串接起来,记录每个串的
s
t
a
r
t
,
e
n
d
start,end
start,end,原先对
[
l
,
r
]
[l,r]
[l,r]的询问就相当于将
[
s
t
l
,
e
d
r
]
[st_l,ed_r]
[stl,edr]的点对应前缀加入后的答案。于是可以对此莫队。复杂度
O
(
(
∑
∣
s
i
∣
)
∗
m
log
m
a
x
l
e
n
)
O\left((\sum|s_i|)*\sqrt m\log maxlen\right)
O((∑∣si∣)∗mlogmaxlen)
考虑把 s e t set set换成链表,但是链表只支持删除,不支持插入(没法找插入前驱后继),但是它支持删除后按顺序回退,所以用回滚莫队,初始时全部加入。每次把左指针指到块左端点,右指针由大到小删除,最后回退即可。
注意维护答案的时候没有出现的前缀可能 A ∗ l e n ( S ) ≥ C A*len(S)\ge C A∗len(S)≥C,但是不能算进去,要判断 f ( S ) f(S) f(S)是否为空(数据没有 v i = 0 v_i=0 vi=0的情况)
Code:
#include<bits/stdc++.h>
#define maxn 300005
#define LL long long
using namespace std;
void read(int &a){
char c;while(!isdigit(c=getchar()));
for(a=c-'0';isdigit(c=getchar());a=a*10+c-'0');
}
const int S = 900;
int n,m,A,B,C,W[maxn],sum,mxl,g[maxn],pos[maxn],st[maxn],ed[maxn],bel[maxn];
LL ans[maxn],res,f[maxn];
struct node{
int l,r,id;
bool operator < (const node &p)const{return bel[l]==bel[p.l]?r>p.r:bel[l]<bel[p.l];}
}q[maxn];
char s[maxn];
int ch[maxn][26],sz,w[maxn],len[maxn],cnt[maxn],L[maxn],R[maxn];
void insert(char *s,int len,int val){
int r=0,v;
for(int i=0;i<len;i++){
if(!ch[r][v=s[i]-'a']) ch[r][v]=++sz,::len[sz]=i+1;
pos[++sum]=r=ch[r][v],w[sum]=val;
}
}
LL F(int x){return 1ll*x*(x-1);}
bool chk(int x){return !f[x]?0:1ll*B*f[x]+1ll*A*len[x]>=C;}
void ins(int x){
int p=pos[x],pre=chk(p); f[p]+=w[x];
if(!pre&&chk(p)&&!cnt[g[len[p]]]++)
p=g[len[p]],res+=F(p-L[p])+F(R[p]-p)-F(R[p]-L[p]),R[L[p]]=L[R[p]]=p;
}
void del(int x){
int p=pos[x],pre=chk(p); f[p]-=w[x];
if(pre&&!chk(p)&&!--cnt[g[len[p]]])
p=g[len[p]],res+=F(R[p]-L[p])-F(p-L[p])-F(R[p]-p),R[L[p]]=R[p],L[R[p]]=L[p];
}
int main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
read(n),read(A),read(B),read(C);
for(int i=1;i<=n;i++) read(W[i]);
for(int i=1;i<=n;i++){
scanf("%s",s); int len=strlen(s); mxl=max(mxl,len);
st[i]=sum+1,ed[i]=sum+len,insert(s,len,W[i]);
}
for(int i=1;i<=mxl;i++) read(g[i]);
for(int i=1;i<=sum;i++) bel[i]=(i-1)/S;
read(m);
for(int i=1,x,y;i<=m;i++) read(x),read(y),q[i].l=st[x],q[i].r=ed[y],q[i].id=i;
for(int i=1;i<=sum;i++) f[pos[i]]+=w[i];
for(int i=1;i<=sz;i++) if(chk(i)) cnt[g[len[i]]]++;
int last=0;
for(int i=1;i<=mxl;i++) if(cnt[i]) R[L[i]=last]=i,res+=F(i-last),last=i;
R[last]=mxl+1,res+=F(R[last]-last);
sort(q+1,q+1+m);
for(int k=0,i=1;k*S+1<=sum&&i<=m;k++){
int lb=k*S+1,rb=min((k+1)*S+1,sum+1),x=lb,y=sum;
for(;i<=m&&bel[q[i].l]==k;i++){
while(y>q[i].r) del(y--);
while(x<q[i].l) del(x++);
ans[q[i].id]=res;
while(x>lb) ins(--x);
}
while(y<sum) ins(++y);
while(x<rb) del(x++);
}
for(int i=1;i<=m;i++){
LL y=F(mxl+1),x=y-ans[i],d=__gcd(x,y);
printf("%lld/%lld\n",x/d,y/d);
}
}
T3:「雅礼集训 2018 Day11」序列
N ≤ 5000 , M ≤ 15000 , a i ≤ 1 0 5 N\le5000,M\le15000,a_i\le10^5 N≤5000,M≤15000,ai≤105
题目分析:
偏序关系,最小化
∑
∣
x
i
−
a
i
∣
\sum |x_i-a_i|
∑∣xi−ai∣,弱化的保序回归
L
1
L_1
L1问题,整体二分,每次求出向
S
{
m
i
d
,
m
i
d
+
1
}
S\{mid,mid+1\}
S{mid,mid+1}取整的最优解,然后划分。详见 IOI2018集训队论文《浅谈保序回归问题》。
实际上代价是
(
x
i
−
a
i
)
k
(x_i-a_i)^k
(xi−ai)k都可以这么做,最小割中划到
m
i
d
mid
mid的点选择
m
i
d
+
1
mid+1
mid+1的代价为原点走到
m
i
d
+
1
mid+1
mid+1的代价减去原点走到
m
i
d
mid
mid的代价。2020年省选联考A卷T3就是保序回归问题,发现线性基的替换结论得到偏序之后就是模板了。(然而省选一周前写过这道题的我并不知道原来这个做法可以做
k
k
k次方…)
Code:
#include<bits/stdc++.h>
#define maxn 50005
#define maxm 500005
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=(j),lim=(k);i<=lim;i++)
using namespace std;
const int inf = 0x3f3f3f3f;
int n,m,a[maxn],ans;
struct node{int l,r,t;};
vector<node>Q[maxn];
int S,T,dis[maxn];
int fir[maxn],cur[maxn],nxt[maxm],to[maxm],c[maxm],tot=1;
void line(int x,int y,int z=inf){
nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,c[tot]=z;
nxt[++tot]=fir[y],fir[y]=tot,to[tot]=x,c[tot]=0;
}
#define lc i<<1
#define rc i<<1|1
int id[maxn],tr[maxn][2],sz;
void build(int i,int l,int r){
if(l==r) return void(tr[i][0]=tr[i][1]=id[l]);
int mid=(l+r)>>1;
tr[i][0]=++sz,tr[i][1]=++sz;
build(lc,l,mid),build(rc,mid+1,r);
line(tr[i][0],tr[lc][0]),line(tr[i][0],tr[rc][0]);
line(tr[lc][1],tr[i][1]),line(tr[rc][1],tr[i][1]);
}
void ins(int i,int l,int r,int x,int y,int p,int t){
if(x<=l&&r<=y) {!t?line(p,tr[i][0]):line(tr[i][1],p); return;}
int mid=(l+r)>>1;
if(x<=mid) ins(lc,l,mid,x,y,p,t);
if(y>mid) ins(rc,mid+1,r,x,y,p,t);
}
bool BFS(){
static int q[maxn],l,r;
memset(dis,-1,(sz+1)<<2),dis[q[l=r=1]=T]=0;
while(l<=r){
int u=q[l++];
for(int i=fir[u],v;i;i=nxt[i]) if(c[i^1]&&dis[v=to[i]]==-1)
dis[q[++r]=v]=dis[u]+1;
}
return ~dis[S];
}
int aug(int u,int augco){
if(u==T) return augco;
int need=augco,dt;
for(int &i=cur[u];i;i=nxt[i]) if(c[i]&&dis[u]==dis[to[i]]+1){
dt=aug(to[i],min(c[i],need)),c[i]-=dt,c[i^1]+=dt;
if(!(need-=dt)) return augco;
}
return augco-need;
}
void solve(int l,int r,vector<int>&A){
if(l==r) {for(int x:A) ans+=abs(a[x]-l); return;}
if(A.empty()) return;
if(A.size()==1) {int x=A[0]; ans+=max(a[x]-R,0)+max(L-a[x],0); return;}
int mid=(l+r)>>1;
S=sz=1,T=++sz;
rep(i,0,A.size()-1) id[i]=++sz;
build(1,0,A.size()-1);
rep(i,0,A.size()-1)
for(node q:G[A[i]]){
int x=lower_bound(A.begin(),A.end(),q.l)-A.begin(),y=upper_bound(A.begin(),A.end(),q.r)-A.begin()-1;
if(x<=y) ins(1,0,A.size()-1,x,y,id[i],q.t);
}
rep(i,0,A.size()-1) a[A[i]]<=mid ? line(id[i],T,1) : line(S,id[i],1);
while(BFS()) memcpy(cur,fir,(sz+1)<<2),aug(S,inf);
BFS();
vector<int>L,R;
rep(i,0,A.size()-1) dis[id[i]]!=-1 ? L.pb(A[i]) : R.pb(A[i]);
memset(fir,0,(sz+1)<<2),tot=1;
solve(l,mid,L),solve(mid+1,r,R);
}
int main()
{
//freopen("sequence.in","r",stdin);
//freopen("sequence.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int x;m--;) {node q; scanf("%d%d%d%d",&q.t,&q.l,&q.r,&x),Q[x].pb(q);}
vector<int>A;
for(int i=1;i<=n;i++) A.pb(i);
solve(*min_element(a+1,a+1+n),*max_element(a+1,a+1+n),A);
printf("%d\n",ans);
}