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;
}
posted @ 2020-08-21 19:42  我亦如此向往  阅读(182)  评论(0编辑  收藏  举报