1. 程式人生 > 實用技巧 >8月21日考試 題解(字首和+差分+貪心+二分答案+縮點+建圖優化)

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;
}