T1:种花
计数题,先考虑 dp,发现不好设计。
再想,C、F 一定是由竖着的线段和横着的线段组成,把它们找到然后计算即可,计算的式子很好推。
时间复杂度最坏 O ( n 4 ) O(n^4) O(n4),也许是数据水了,能拿 75 p t s 75\space pts 75 pts。
考虑优化,发现这些横着的线段的长度可以前缀和 O ( 1 ) O(1) O(1) 计算,我们枚举每一列,按行从上到下计算即可。
时间复杂度 O ( T n m ) O(Tnm) O(Tnm)。
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define ll long long
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
const int N=1010,mod=998244353;
int T,id,n,m,c,ff,a[N][N],f[N][N];
int main(){
T=rd,id=rd;
while(T--){
memset(f,0,sizeof(f));
n=rd,m=rd,c=rd,ff=rd;
FOR(i,1,n) FOR(j,1,m) scanf("%1d",&a[i][j]);
int C=0,F=0;
FOR(i,1,n) ROF(j,m-1,1){
if(a[i][j]) f[i][j]=-1;
else if(a[i][j+1]==0) f[i][j]=f[i][j+1]+1;
}
FOR(j,1,m-1){
int cntc=0,cntf=0;
FOR(i,1,n){
if(f[i][j]==-1){cntc=cntf=0;continue;}
C=(1ll*C+1ll*cntc*f[i][j])%mod;
F=(1ll*F+cntf)%mod;
cntf=(cntf+1ll*f[i][j]*cntc%mod)%mod;
cntc+=max(0,f[i-1][j]);
}
}
printf("%d %d\n",c*C,ff*F);
}
return 0;
}
T2:P8866 [NOIP2022] 喵了个喵
k k k 只有 2 n − 1 , 2 n − 2 2n-1,2n-2 2n−1,2n−2 两种取值,如果 k = 2 n − 2 k=2n-2 k=2n−2,即 2 ( n − 1 ) 2(n-1) 2(n−1),那么我们可以拿出 n − 1 n-1 n−1 个栈来放数,最后留一个栈(称其为特殊栈)来消除底部的数,容易证明这样肯定可以得到合法方案。
再看 k = 2 n − 1 k=2n-1 k=2n−1,这样会出现 2 n − 2 2n-2 2n−2 个数放完了,还有一个多出来的数,考虑这个数的归宿。
设多出来的数为 w w w,我们找到在它之后的最先出现在某个栈的底部的数 u u u,设 u u u 所在栈为 x x x,该栈栈顶为 v v v,进行分讨。
- 若 w w w 比 u u u 先出现,那么我们将 w w w 放入特殊栈,因为之后用不到 2 2 2 操作。
- 否则比较 u , v u,v u,v,若 u u u 比 v v v 先出现,那么之后需要借用特殊栈消除 u u u,所以 w w w 放到 x x x 栈顶。
- 否则就是 v < u < w v<u<w v<u<w,我们将 w w w 放入特殊栈,之后会出现 v , u v,u v,u 依次被消除的情况,此时 x x x 又空了,所以我们将 x x x 设为新的特殊栈。
本题实现细节较多,简单说一下:
- 记录每个数所在栈。
- 用队列来维护当前有哪些空栈,注意某个数出栈时,该栈有空余位置后要重新入队。
- 1 , 2 1,2 1,2 操作分别写成函数,方便维护。
最后还有一个时间复杂度的问题,我们找 u , v u,v u,v 的出现顺序时,能否暴力往后找?答案是可以的,因为只有出现所有非特殊栈放满后,才会去找,而下一次再出现这种情况的位置,一定不会和之前重合,所以最多只会循环 m m m 次。
时间复杂度 O ( ∑ m ) O(\sum m) O(∑m)。
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
#define debug cout<<"FUCKCCF!"<<endl;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
const int N=2e6+10;
int T,n,m,k,a[N],cnt,spe,belong[N];
struct node{int op,x,y;}ans[N<<2];
vector<deque<int> > s(310);
queue<int> q;
void opera1(int x,int t){//1操作
ans[++cnt]={1,x};
if(s[x].size()>0&&s[x].back()==t){
s[x].pop_back();
if(x!=spe&&s[x].size()<2) q.push(x);
belong[t]=0;
}
else{
s[x].push_back(t);
belong[t]=x;
}
}
void opera2(int x,int y){//2操作
ans[++cnt]={2,x,y};
belong[s[x].front()]=0;
s[x].pop_front(),s[y].pop_front();
if(s[x].size()<2) q.push(x);
}
void init(){
while(!q.empty()) q.pop();
cnt=0;s.clear();s.resize(310);
FOR(i,0,k) belong[i]=0;
}
int main(){
T=rd;
while(T--){
init();n=rd,m=rd,k=rd;
FOR(i,1,m) a[i]=rd;
FOR(i,1,n-1) q.push(i),q.push(i);
spe=n;
FOR(i,1,m){
int x=belong[a[i]];
if(x){//之前出现过
if(s[x].size()==1) opera1(x,a[i]);
else if(s[x].back()==a[i]) opera1(x,a[i]);
else opera1(spe,a[i]),opera2(x,spe);
}
else if(q.size()>0){//有空栈
x=q.front();q.pop();
opera1(x,a[i]);
}
else{
for(int j=i+1;;j++){
if(a[j]==a[i]) break;
if(s[belong[a[j]]].front()==a[j]){x=belong[a[j]];break;}
}
if(!x) opera1(spe,a[i]);//w<u
else{
int u=s[x].front(),v=s[x].back();
for(int j=i+1;;j++){
if(a[j]==u){//w>v>u
opera1(x,a[i]);
break;
}
else if(a[j]==v){//w>u>v
opera1(spe,a[i]),q.push(spe),spe=x;
break;
}
}
}
}
}
printf("%d\n",cnt);
FOR(i,1,cnt){
if(ans[i].op==1) printf("%d %d\n",ans[i].op,ans[i].x);
else printf("%d %d %d\n",ans[i].op,ans[i].x,ans[i].y);
}
}
return 0;
}
T3:P8867 [NOIP2022] 建造军营
不难发现只有割边才是关键的,所以先跑边双缩点,然后转化为树后考虑树形 dp。
设 f x , 0 / 1 f_{x,0/1} fx,0/1 表示 x x x 节点是否建造军营的方案数,发现很难转移,所含的状态太多了。
改变含义,令 f x , 0 / 1 f_{x,0/1} fx,0/1 表示以 x x x 为根的子树内是否有军营,若有,则都通过某些边与 x x x 连通,并且不选 x x x 与 f a x fa_x fax 的连边的方案数。
为方便统计,强制令 x x x 子树外不建军营,则答案为 ∑ f x , 1 × x \sum f_{x,1}\times x ∑fx,1×x 子树外边数,注意特判 x x x 为根节点情况。
边双缩完点后,设 V x V_x Vx 表示 x x x 代表边双内点数, E x E_x Ex 表示边数,初始化显然: f x , 1 = 2 V x , f x , 0 = 2 V x + E x − f x , 1 f_{x,1}=2^{V_x},f_{x,0}=2^{V_x+E_x}-f_{x,1} fx,1=2Vx,fx,0=2Vx+Ex−fx,1。设边 ( x , y ) (x,y) (x,y),转移时 f x , 0 f_{x,0} fx,0 较为显然,只能是 f y , 0 f_{y,0} fy,0,边可以选和不选。转移 f x , 1 f_{x,1} fx,1 时考虑之前没有建军营,在 y y y 才建,以及之前建过,此时 y y y 建不建都行。
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define ll long long
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
#define graph(t,edge,head) for(int i=head[t];i;i=edge[i].nxt)
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
const int N=5e5+10,mod=1e9+7;
int n,m,head[N],tot=1,he[N],tot2,f[N][2],V[N],E[N],ecnt[N],ans;
int dfn[N],low[N],tim,cnt,dcc[N],stk[N],top,bri[N<<2];
struct node{int from,to,nxt;}edge[N<<2],e[N<<2];
void add(int x,int y){edge[++tot]={x,y,head[x]},head[x]=tot;}
void ADD(int x,int y){e[++tot2]={x,y,he[x]},he[x]=tot2;}
int qpow(int a,int b){
int res=1;
while(b){if(b&1)res=1ll*res*a%mod;a=1ll*a*a%mod,b>>=1;}
return res;
}
void tarjan(int x,int in_edge){
dfn[x]=low[x]=++tim,stk[++top]=x;
graph(x,edge,head){
int y=edge[i].to;
if(!dfn[y]){
tarjan(y,i);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]) bri[i]=bri[i^1]=1;
}
else if((in_edge^1)!=i) low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
int y;cnt++;do{
y=stk[top--],dcc[y]=cnt,V[cnt]++;
}while(x!=y);
}
}
void dfs(int x,int fa){
ecnt[x]=E[x];
graph(x,e,he){
int y=e[i].to;if(y==fa) continue;
dfs(y,x);
ecnt[x]+=ecnt[y]+1;
}
}
void dp(int x,int fa){
graph(x,e,he){
int y=e[i].to;if(y==fa) continue;
dp(y,x);
f[x][1]=(1ll*f[x][0]*f[y][1]%mod+1ll*f[x][1]*(2ll*f[y][0]+f[y][1])%mod)%mod;
f[x][0]=2ll*f[x][0]*f[y][0]%mod;
}
if(x==1) ans=(1ll*ans+f[x][1])%mod;
else ans=(1ll*ans+1ll*f[x][1]*qpow(2,ecnt[1]-ecnt[x]-1))%mod;
}
int main(){
n=rd,m=rd;
FOR(i,1,m){int x=rd,y=rd;add(x,y),add(y,x);}
FOR(i,1,n) if(!dfn[i]) tarjan(i,0);
FOR(i,2,tot) if(bri[i]) ADD(dcc[edge[i].from],dcc[edge[i].to]);
FOR(i,2,tot) if(!bri[i]) E[dcc[edge[i].from]]++;
FOR(i,1,cnt){
E[i]>>=1;
f[i][0]=qpow(2,E[i]),f[i][1]=qpow(2,V[i]+E[i])-f[i][0];
}
dfs(1,0),dp(1,0);
printf("%d\n",ans);
return 0;
}
T4:比赛
暴力做是 8 p t s 8\space pts 8 pts 的,考虑优化。
把询问离线下来,扩展右端点,设右端点为 r r r, x j = max k = j r { a k } , y j = max k = j r { b k } , f i = ∑ j = i r x j y j x_j=\max_{k=j}^{r}\{a_k\},y_j=\max_{k=j}^{r}\{b_k\},f_i=\sum_{j=i}^{r}x_jy_j xj=maxk=jr{ak},yj=maxk=jr{bk},fi=∑j=irxjyj,则对于右端点为 r r r 的询问,其答案为 ∑ i = l r f i \sum_{i=l}^{r}f_i ∑i=lrfi,可以达到 20 p t s 20\space pts 20 pts。
20 p t s 20\space pts 20 pts 代码见下:
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define PII pair<int,int>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define ull unsigned long long
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
const int N=3e5+10;
int T,n,q,a[N],b[N],x[N],y[N];ull f[N],ans[N];
vector<PII> d[N];
int main(){
T=rd,n=rd;
FOR(i,1,n) a[i]=rd;FOR(i,1,n) b[i]=rd;
q=rd;
FOR(i,1,q){int l=rd,r=rd;d[r].pb(mp(l,i));}
FOR(r,1,n){
FOR(i,1,r) x[i]=max(x[i],a[r]),y[i]=max(y[i],b[r]),f[i]+=x[i]*y[i];
for(auto i:d[r]){
int l=i.fi,id=i.se;
FOR(j,l,r) ans[id]+=f[j];
}
}
FOR(i,1,q) printf("%llu\n",ans[i]);
return 0;
}
继续优化求 f f f 的过程,很容易想到利用线段树。
线段树维护 f f f 值, x y xy xy 乘积之和, x x x 的和, y y y 的和。
先预处理出每个 a i , b i a_i,b_i ai,bi 可以影响到最远多远,即 i i i 左边第一个大于它的数之后,这是一段后缀,我们会进行区间赋值操作,然后将 [ 1 , r ] [1,r] [1,r] 内的所有 f i f_i fi 值加上 x i y i x_iy_i xiyi,最后区间求和即可,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
线段树查询,标记与信息的合并是好做的,关键是标记与标记的合并。我们首先会维护 6 6 6 个标记:
- c x , c y , a d d x y , a d d x , a d d y , a d d c cx,cy,addxy,addx,addy,addc cx,cy,addxy,addx,addy,addc,表示区间赋值的标记,区间增量的标记。
规定区间增量的标记更新在赋值标记前。
对于只赋值 c x cx cx 的区间, c y , a d d y , a d d c cy,addy,addc cy,addy,addc 无影响, a d d x y ∑ x i y i addxy\sum x_iy_i addxy∑xiyi 会变为 a d d x y × c x ∑ y i addxy\times cx\sum y_i addxy×cx∑yi,所以 a d d x y × c x → a d d y addxy\times cx\to addy addxy×cx→addy。
只赋值 c y cy cy 的区间同理。
对于
c
x
,
c
y
cx,cy
cx,cy 均不为
0
0
0 的区间,
a
d
d
x
y
∑
x
i
y
i
=
a
d
d
x
y
×
c
x
×
c
y
×
l
e
n
addxy\sum x_iy_i=addxy\times cx\times cy\times len
addxy∑xiyi=addxy×cx×cy×len,
所以
a
d
d
x
y
×
c
x
×
c
y
→
a
d
d
c
addxy\times cx\times cy\to addc
addxy×cx×cy→addc。
然后合并即可,具体实现细节可见代码。
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define PII pair<int,int>
#define mp make_pair
#define ull unsigned long long
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
const int N=3e5+10;
int T,n,q,a[N],b[N],la[N],lb[N];ull ans[N];
vector<PII> d[N];
struct node{ull s,xy,x,y;}tr[N<<2];
struct TAG{ull cx,cy,addxy,addx,addy,addc;}tag[N<<2];
void change(int u,int l,int r,TAG t){
if(tag[u].cx&&tag[u].cy){
tag[u].addc+=t.addxy*tag[u].cx*tag[u].cy+t.addx*tag[u].cx+t.addy*tag[u].cy+t.addc;
}
else if(tag[u].cx){
tag[u].addc+=t.addx*tag[u].cx+t.addc;
tag[u].addy+=t.addxy*tag[u].cx+t.addy;
}
else if(tag[u].cy){
tag[u].addc+=t.addy*tag[u].cy+t.addc;
tag[u].addx+=t.addxy*tag[u].cy+t.addx;
}
else{
tag[u].addxy+=t.addxy;
tag[u].addx+=t.addx;
tag[u].addy+=t.addy;
tag[u].addc+=t.addc;
}
if(t.cx) tag[u].cx=t.cx;
if(t.cy) tag[u].cy=t.cy;
int len=r-l+1;
tr[u].s+=t.addxy*tr[u].xy+t.addx*tr[u].x+t.addy*tr[u].y+t.addc*len;
if(t.cx&&t.cy){
tr[u].xy=t.cx*t.cy*len;
tr[u].x=t.cx*len,tr[u].y=t.cy*len;
}
else if(t.cx){
tr[u].xy=t.cx*tr[u].y;
tr[u].x=t.cx*len;
}
else if(t.cy){
tr[u].xy=t.cy*tr[u].x;
tr[u].y=t.cy*len;
}
}
void pushdown(int u,int l,int r){
int mid=(l+r)>>1;
change(u<<1,l,mid,tag[u]),change(u<<1|1,mid+1,r,tag[u]);
tag[u]={0,0,0,0,0,0};
}
void pushup(int u){
tr[u].s=tr[u<<1].s+tr[u<<1|1].s;
tr[u].x=tr[u<<1].x+tr[u<<1|1].x;
tr[u].y=tr[u<<1].y+tr[u<<1|1].y;
tr[u].xy=tr[u<<1].xy+tr[u<<1|1].xy;
}
void modify(int u,int l,int r,int ql,int qr,TAG t){
if(ql<=l&&r<=qr){
change(u,l,r,t);
return;
}
pushdown(u,l,r);int mid=(l+r)>>1;
if(ql<=mid) modify(u<<1,l,mid,ql,qr,t);
if(qr>mid) modify(u<<1|1,mid+1,r,ql,qr,t);
pushup(u);
}
ull query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr) return tr[u].s;
pushdown(u,l,r);int mid=(l+r)>>1;ull ans=0;
if(ql<=mid) ans+=query(u<<1,l,mid,ql,qr);
if(qr>mid) ans+=query(u<<1|1,mid+1,r,ql,qr);
return ans;
}
int main(){
T=rd,n=rd;
FOR(i,1,n) a[i]=rd;FOR(i,1,n) b[i]=rd;
a[0]=b[0]=N;
FOR(i,1,n){
la[i]=i,lb[i]=i;
while(a[i]>a[la[i]-1]) la[i]=la[la[i]-1];
while(b[i]>b[lb[i]-1]) lb[i]=lb[lb[i]-1];
}
q=rd;
FOR(i,1,q){int l=rd,r=rd;d[r].push_back(mp(l,i));}
FOR(i,1,n){
modify(1,1,n,la[i],i,(TAG){(ull)a[i],0,0,0,0,0});
modify(1,1,n,lb[i],i,(TAG){0,(ull)b[i],0,0,0,0});
modify(1,1,n,1,i,(TAG){0,0,1,0,0,0});
for(auto j:d[i]){
ans[j.second]=query(1,1,n,j.first,i);
}
}
FOR(i,1,q) printf("%llu\n",ans[i]);
return 0;
}