Codeforces Round #578 (Div. 2)

D - White Lines

Description

*1900

给出一个 $n\ast m$ 的黑白矩阵,你可以将一块 $k\ast k$ 的矩形全部变成白色。
问你执行一次上述染色之后,全空白的行和全空白的列数量总和的最大值。

数据范围:$n,m\leq 2000$。

Solution

$Fl(i,j)$ 表示擦除了第 $i$ 行 $j$ 列开始横着的 $k$ 个格子之后这一行是否能为空;
$Fc(i,j)$ 表示擦除了第 $i$ 行 $j$ 列开始竖着的 $k$ 个格子之后这一列是否能为空;

对这两个做前缀和,然后枚举左上角即可。

Code

#include<bits/stdc++.h>
using namespace std;

inline int read(){
    int ret=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}

int read_ch(){
    char ch=getchar();
    while (ch!='B' && ch!='W') ch=getchar();
    return ch=='B';
}

const int maxn=2005;
int n,k,ben=0,ans=0;
int a[maxn][maxn];

int fl[maxn][maxn],suml[maxn][maxn];
int fc[maxn][maxn],sumc[maxn][maxn];

signed main(){
    n=read(); k=read();
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            a[i][j]=read_ch();
    
    for (int i=1;i<=n;i++){
        int tl=-1,tr=-1;
        for (int j=1;j<=n;j++) if (a[i][j]) {tl=j;break;}
        for (int j=n;j>=1;j--) if (a[i][j]) {tr=j;break;}
        if (tl==-1){
            ben++;
            continue;
        }
        if (tr-tl+1 > k) continue;
        for (int j=tl-(k-(tr-tl+1));j<=tl;j++) fl[i][j]=1;
    }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n-k+1;j++)
            suml[i][j]=suml[i-1][j]+fl[i][j];

    for (int j=1;j<=n;j++){
        int tl=-1,tr=-1;
        for (int i=1;i<=n;i++) if (a[i][j]) {tl=i;break;}
        for (int i=n;i>=1;i--) if (a[i][j]) {tr=i;break;}
        if (tl==-1){
            ben++;
            continue;
        }
        if (tr-tl+1 > k) continue;
        for (int i=tl-(k-(tr-tl+1));i<=tl;i++) fc[i][j]=1;
    }
    for (int j=1;j<=n;j++)
        for (int i=1;i<=n-k+1;i++)
            sumc[i][j]=sumc[i][j-1]+fc[i][j];

    for (int i=1;i<=n-k+1;i++)
        for (int j=1;j<=n-k+1;j++){
            ans=max(ans,ben + suml[i+k-1][j]-suml[i-1][j]+sumc[i][j+k-1]-sumc[i][j-1]);
        }
    printf("%d\n",ans);
    return 0;
}

E - Compress Words

Description

*2000

定义合并两个单词为:移除后一个单词的前缀,这个前缀和前一个单词的后缀相同并且最长。例如:sample+please=samplease
给出 n 个单词,从左到右两两进行合并(即先合并第一个和第二个,然后将结果合并第三个……),输出所有操作完成后的结果。$1\leq n\leq 10^5$,所有单词总长度不超过 $10^6$。

Solution #1

(来自:https://www.luogu.org/blog/Soulist/solution-cf1200e

感觉这题字符串哈希的做法比较简单……
对新加入的字符串直接枚举其前缀和原串的后缀,然后双模哈希判断相同……
(据说 CF 写哈希很容易被 hack?)

Code #1

#include<bits/stdc++.h>
using namespace std;

#define int long long

inline int read(){
    int ret=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}

const int maxn=10005,maxlen=1e6+5,LEN=1e6;
const int tt=1e9+7;
const int hv[2]={1926,817};

int n;
char s[maxlen],ans[maxlen];
int len=0;

int qsm[2][maxlen];

signed main(){
    n=read();
    qsm[0][0]=qsm[1][0]=1;
    for (int i=1;i<=LEN;i++) qsm[0][i]=qsm[0][i-1]*hv[0]%tt,qsm[1][i]=qsm[1][i-1]*hv[1]%tt;

    while (n--){
        scanf("%s",s+1); int nowlen=strlen(s+1);
        int hash1[2],hash2[2],now=0;
        hash1[0]=hash1[1]=hash2[0]=hash2[1]=0;
        for (int i=1;i<=min(nowlen,len);i++){
            for (int k=0;k<2;k++){
                hash1[k]=(hash1[k]*hv[k]+s[i])%tt;
                hash2[k]=(hash2[k]+ans[len-i+1]*qsm[k][i-1])%tt;
            }
            if (hash1[0]==hash2[0] && hash1[1]==hash2[1]) now=i;
        }
        for (int i=now+1;i<=nowlen;i++) ans[++len]=s[i];
    }
    for (int i=1;i<=len;i++) putchar(ans[i]);
    printf("\n");
    return 0;
}

Solution #2

(来自 CF 官方 Tutorial)

用 KMP 的解法需要对 KMP 的原理等有比较深入的认知(比如我就没想出来(吐血))。
KMP 中 next 表示的就是最长相同前缀和后缀的长度,所以只要把两个串先拼在一起,使要找前缀的在前面、要找后缀的在后面,然后再构造一遍 next 数组不就行了??最后一个字符的 next 就是答案。
需要注意的是,将两个字符串“拼在一起”时要在中间加一个特殊的字符(比如@),以确保前缀和后缀不会越过一个字符串。如果新加入的串长度比已合并的更大,可以把前面截掉,只留后面和已合并的长度一样的后缀。

F - Graph Traveler

Description

*1500

给出一张有向图,可能有重边、自环。每个点有点权 $k_i$,点 $i$ 有 $m_i$ 条出边,连向的点分别记为 $e_i[0],e_i[1],\dots,e_i[m_i-1]$。
你可以开始一个 Graph Traveler 的游戏,规则(步骤)如下:

显然步骤 2 和 3 会陷入循环。现在给出 $q$ 组询问,每次询问如果从给定的点 $x$ 以给定的数字 $y$ 开始,多少点会被经过无数次。

数据范围:$1\leq n\leq 1000, 1\leq m_i\leq 10, -10^9\leq k_i\leq 10^9, 1\leq m_i\leq 10$;
$1\leq q\leq 10^5, -10^9\leq y\leq 10^9$。

Solution

如果直接暴力求解,我们肯定想要的是记录一个状态 $(x,c)$,表示走到 $x$ 点,手中的数字是 $y$,如果走到重复的状态则会陷入循环。但是问题在于这个 $y$ 可以是无限大或者无限小。
考虑对于一个点 $x$,在走到这个点上时持有的数字如果是 $y$ 和 $y+m_i$,结果是一样的。那么如对于所有点,就都满足 $y$ 和 $y+lcm(m_i)$ 同等。$lcm(1,\dots,10) = 2520$(记为 $maxs$),所以我们可以把所有 $y$ 都控制到 $[0,2520)$。
接下来就简单了,可以把每个 $(x,y)$ 状态看成一个点,编号为 $(x-1)\ast maxs+y$,那么每个点就只有一条出边,直接求环就可以了……

Code

long long 过不了,可能会爆内存,但是显示的是 RE……

#include<bits/stdc++.h>

using namespace std;

// #define int long long

inline int read(){
    int ret=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}

const int maxn=2005,maxs=2520;

int n,v[maxn];
int m[maxn],k[15],lnk[maxn*maxs],rlnk[maxn*maxs];
int q;

int ans[maxn*maxs];
int deep[maxn*maxs],onl[maxn*maxs];
bool vis[maxn*maxs],cnd[maxn];

int DFS(int x,int y,int fa){
    int now=(x-1)*maxs+y;
    if (ans[now]!=-1) return ans[now];

    deep[now]=deep[fa]+1; onl[deep[now]]=x;
    vis[now]=true;
    if (vis[lnk[now]]){
        for (int i=deep[lnk[now]];i<=deep[now];i++) if (!cnd[onl[i]]) ans[now]++,cnd[onl[i]]=true;
        for (int i=deep[lnk[now]];i<=deep[now];i++) cnd[onl[i]]=false;
    } else ans[now]=DFS(rlnk[now],(y+v[x])%maxs,now);
    vis[now]=false;
    return ans[now];
}

signed main(){
    n=read();
    for (int i=1;i<=n;i++) v[i]=read(),v[i]=(v[i]%maxs+maxs)%maxs;
    for (int i=1;i<=n;i++){
        m[i]=read();
        for (int j=0;j<m[i];j++) k[j]=read();
        for (int j=0;j<maxs;j++){
            int nowto=(j+v[i])%maxs;
            lnk[(i-1)*maxs+j]=(k[nowto%m[i]]-1)*maxs+nowto;
            rlnk[(i-1)*maxs+j]=k[nowto%m[i]];
            ans[(i-1)*maxs+j]=-1;
        }
    }

    for (int i=1;i<=n;i++)
        for (int j=0;j<maxs;j++)
            if (ans[(i-1)*maxs+j]==-1) DFS(i,j,(i-1)*maxs+j);
    
    q=read();
    while (q--){
        int x=read(),c=read();
        c=(c%maxs+maxs)%maxs;
        printf("%d\n",ans[(x-1)*maxs+c]+1);
    }
    return 0;
}