8月21日考试 题解(前缀和+差分+贪心+二分答案+缩点+建图优化)
今天考的还行,主要暴力分给力233
T1 棋盘
题目大意:给定一张$n*n$的棋盘,每个格子上是黑色或白色。现在有一次机会将一个$k*k$的区域染成白色。问操作过后全部为白色的行+全部为白色的列最多有多少。
正解是前缀和+差分。然而因为时限比较宽松,打了一个$(n-k+1)^2k$的暴力也能过2333。姑且看看吧。
代码:
#include<bits/stdc++.h> using namespace std; int a[2005][2005],n,k,sum1[2005][2005],sum2[2005][2005]; int sum,ans; char ch[2005][2005]; inline void solve(int x,int y) { int res=0; for (int i=x;i<=x+k-1;i++) { int s1=sum1[i][y+k-1]-sum1[i][y-1]; if (sum1[i][n]&&s1==sum1[i][n]) res++; } for (int i=y;i<=y+k-1;i++) { int s2=sum2[x+k-1][i]-sum2[x-1][i]; if (sum2[n][i]&&s2==sum2[n][i]) res++; } ans=max(ans,sum+res); } int main() { scanf("%d%d",&n,&k); for (int i=1;i<=n;i++) { scanf("%s",ch[i]+1); for (int j=1;j<=n;j++) { a[i][j]=(ch[i][j]=='B'); sum1[i][j]=sum1[i][j-1]+a[i][j]; sum2[i][j]=sum2[i-1][j]+a[i][j]; } } for (int i=1;i<=n;i++) { if (!sum1[i][n]) sum++; if (!sum2[n][i]) sum++; } for (int i=1;i<=n-k+1;i++) for (int j=1;j<=n-k+1;j++) solve(i,j); printf("%d",ans); return 0; }
T2 序列
题目大意:给定一个长度为$n$的序列。现将其划分成若干个区间,使得区间内最大值减最小值的和最大。求出这个最大值。
一眼看出$n^2$的DP。设$f[i]$表示考虑到$i$时的最大值,显然有$f[i]=\max\limits_{0\leq j\leq i-1}(f[i],f[j]+qmax-qmin)$,$qmax$和$qmin$指$[j+1,i]$内的最值。
然而正解是$O(n)$的贪心+递推。序列内元素的值变化可以看成一条波浪,而划分一定在波峰和波谷,不然不是最优的。我们所要考虑的是在最值的左边还是右边划一刀。这样我们可以从左到右扫一遍序列进行决策即可。
代码:
#include<bits/stdc++.h> #define int long long using namespace std; const int inf=1e18; const int maxn=10000005; const int maxm=500005; const int mod=1<<30; int a[maxn],b[maxn]; int x,y,z,m,p[maxm],l[maxm],r[maxm],n; int maxx=0,minn=inf,ans[2],pre; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } signed main() { n=read(); x=read(),y=read(),z=read(),b[1]=read(),b[2]=read(),m=read(); for (int i=3;i<=n;i++) b[i]=(x*b[i-1]+y*b[i-2]+z)%mod; for (int i=1;i<=m;i++) { p[i]=read(),l[i]=read(),r[i]=read(); for (int j=p[i-1]+1;j<=p[i];j++) a[j]=b[j]%(r[i]-l[i]+1)+l[i]; } ans[0]=-inf,ans[1]=0; for (int i=1;i<=n;i++) if (i>1&&i<n&&((a[i-1]<a[i]&&a[i]>=a[i+1])||(a[i-1]>a[i]&&a[i]<=a[i+1]))) { int tmp[2]={-inf,-inf}; tmp[0]=max(tmp[0],ans[0]+max(maxx,a[pre])-min(minn,a[pre])); if (pre!=i-1) tmp[0]=max(tmp[0],ans[1]+maxx-minn); else tmp[0]=max(tmp[0],ans[1]); tmp[1]=max(tmp[1],ans[0]+max(a[pre],max(maxx,a[i]))-min(a[pre],min(minn,a[i]))); tmp[1]=max(tmp[1],ans[1]+max(maxx,a[i])-min(minn,a[i])); ans[0]=tmp[0],ans[1]=tmp[1]; maxx=0,minn=inf,pre=i; } else maxx=max(maxx,a[i]),minn=min(minn,a[i]); printf("%lld",max(ans[0]+max(maxx,a[pre])-min(minn,a[pre]),ans[1]+maxx-minn)); return 0; }
T3 游戏
给定一张$n$个点$m$条边的无向图,$q$次询问,支持点向区间连边。显然一共有$q+1$张图。定义合法点对$(u,v)$为从$u$出发经过$x$到$v$的路径个数是有限的,路径可以重复经过点和边但不能相同。如果一张图的合法点对个数不少于$k$,我们就说这张图是合法的。现在问你这$q+1$张图中有多少个是合法的。
二分答案+缩点+优化建图。
首先合法的图不可能超过$k+1$个,所以我们不妨进行二分答案。
不难想到如果$u->x->v$的路径上有点是在环上的话那么$(u,v)$肯定不是合法点对。所以我们不妨进行缩点,然后找出满足条件的点,然后乘法原理进行统计。
对于支持点向区间连边,我们可以$n\log n$建出每个区间对应的虚点,虚点向区间内所有点连边。当需要向区间连边的时候直接向虚点连边即可。
其实点向区间连边也可以用线段树优化建图,但我并不会写QAQ。之前写过但考试肯定写不出来……
代码:
#include<cstdio> #include<iostream> #include<cstring> #include<stack> #include<vector> using namespace std; const int maxn=200005; int n,m,x0,q,num,maxlen,last;long long k; int x[maxn],l[maxn],r[maxn]; int siz[maxn],pos[maxn],dfn[maxn],low[maxn],vis[maxn],jishu,tot; int first[maxn],t1[maxn],t2[maxn],single[maxn]; int head[maxn],cnt,backup[maxn],backupsz; struct node { int next,to; }edge[maxn*25]; vector<int> v1[maxn],v2[maxn]; stack<int> st; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void add(int from,int to) { edge[++cnt].next=head[from]; edge[cnt].to=to; head[from]=cnt; } inline void tarjan(int now) { dfn[now]=low[now]=++jishu; vis[now]=1;st.push(now); for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; if (!dfn[to]) tarjan(to),low[now]=min(low[now],low[to]); else if (vis[to]) low[now]=min(low[now],dfn[to]); } if (low[now]==dfn[now]) { tot++; while(st.top()!=now) { int x=st.top();st.pop(); vis[x]=0; pos[x]=tot; } int x=st.top();st.pop(); vis[x]=0; pos[x]=tot; } } inline void link(int x0,int l0,int r0,int len) { if (l0>r0) return; if (r0-l0+1<len){link(x0,l0,r0,len>>1);return;} int l1=(l0-1)/len+1; if (len*(l1-1)+1==l0) add(x0,first[len]+l1),link(x0,len*l1+1,r0,len); else link(x0,l0,len*l1,len),link(x0,len*l1+1,r0,len); return; } inline long long check(int mid) { memcpy(backup,head,sizeof(backup)); backupsz=cnt; for (int i=last+1;i<=mid;i++) link(x[i],l[i],r[i],maxlen); memset(siz,0,sizeof(siz)); memset(pos,0,sizeof(pos)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); jishu=tot=0; for (int i=1;i<=num;i++) if (!dfn[i]) tarjan(i); for (int i=1;i<=tot;i++) single[i]=0; for (int i=1;i<=num;i++) v1[i].clear(),v2[i].clear(); for (int i=1;i<=n;i++) single[pos[i]]=1,siz[pos[i]]++; for (int i=1;i<=num;i++) for (int j=head[i];j;j=edge[j].next) { int u=pos[i],v=pos[edge[j].to]; if (u!=v) v1[u].push_back(v),v2[v].push_back(u); if (i<=n&&edge[j].to<=n&&u==v) single[u]=0; } memset(t1,0,sizeof(t1)); memset(t2,0,sizeof(t2)); t1[pos[x0]]=t2[pos[x0]]=1; int cnt1=0,cnt2=0,c1=n,c2=n; for (int i=1;i<=tot;i++) { if (!t2[i]) continue; c2-=siz[i]; for (int j=0;j<v2[i].size();j++) t2[v2[i][j]]=1; } for (int i=1;i<=tot;i++) if (single[i]||!siz[i]) t2[i]=0; for (int i=1;i<=tot;i++) { if (!t2[i]) continue; cnt2+=siz[i]; for (int j=0;j<v2[i].size();j++) t2[v2[i][j]]=1; } for (int i=tot;i>=1;i--) { if (!t1[i]) continue; c1-=siz[i]; for (int j=0;j<v1[i].size();j++) t1[v1[i][j]]=1; } for (int i=tot;i>=1;i--) if (single[i]||!siz[i]) t1[i]=0; for (int i=tot;i>=1;i--) { if (!t1[i]) continue; cnt1+=siz[i]; for (int j=0;j<v1[i].size();j++) t1[v1[i][j]]=1; } return (long long)(n-cnt1)*(long long)(n-cnt2)+(long long)c1*(long long)cnt2+(long long)c2*(long long)cnt1; } inline void reset() { memcpy(head,backup,sizeof(head)); cnt=backupsz; } signed main() { n=read();m=read();x0=read();q=read();k=read(); for (int i=1;i<=m;i++) { int u=read(),v=read(); add(u,v); } num=n,maxlen=1; for (int i=2;i<=n;i<<=1) { first[i]=num,maxlen=i; for (int j=1;j+i-1<=n;j+=i) { num++; for (int k=j;k<j+i;k++) add(num,k); } } for (int i=1;i<=q;i++) x[i]=read(),l[i]=read(),r[i]=read(); int l0=0,r0=q+1; while(l0<r0) { int mid=(l0+r0+1)/2ll; if (check(mid-1)>=k) l0=mid,last=mid-1; else r0=mid-1,reset(); } printf("%d",l0); return 0; }