Rikka with Seam
Time Limit: 16000/8000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 164 Accepted Submission(s): 50
Problem Description
Seam carving is a novel algorithm for resizing images while maintaining as much information as possible from the source image.
Now, Rikka is going to use seam carving method to deal with an n×m black and white picture. We can abstract this picture into an n×m 01 matrix A.
A K-seam of this picture is an integer sequence a which satisfies the following conditions:
1. |a|=n, ai∈[1,m].
2. |ai−ai+1|≤K, ∀i∈[1,n).
After choosing a K-seam a, Rikka reduces the size of the picture by deleting pixels (i,ai), and then she gets a matrix A′ of size n×(m−1).
Rikka finds that deleting different seams may get the same result. In the previous example, seam [1,2,3],[1,2,1],[1,2,2],[1,1,1] are equivalent.
Now Rikka wants to calculate the number of different matrixes she can get by deleting exactly one K-seam.
Input
The first line contains a single integer t(1≤t≤103), the numebr of testcases.
For each testcase, the first line contains three numbers n,m,K(2≤n,m≤2×103,1≤K≤m).
Then n lines follow, each line contains a 01 string of length m which describes one row of the matrix.
The input guarantees that there are at most 5 testcases with max(n,m)>300.
Output
For each testcase, output a single line with a single number, the answer modulo 998244353.
题意:
给你一个n*m大小的01矩阵,对于这个矩阵的每一行,你都必须标记其中一个位置并且将这个位置上的数字删去, 并且要求相邻两行删去的位置之间的横线距离不能超过k,这样可以得到一个n行m-1列的01矩阵,问最后能得到多少种不同的矩阵
思路:
对于每一行中任意一段连续的0或者连续的1,很显然删除其中的任意一个数字,最后的结果都是相同的,例如1100011,删除前两个1,中间三个0,或者后两个1中的其中一个,得出的结果都相同(100011、110011、110001)
可以将矩阵转化一下:对于每一行,第i个数字变成前是第几段连续的0或1,如下
这样问题就变成了,从矩阵中每一行选出一个数字,其中相邻两行选出的数字之间的横线距离不能超过k,这样可以得到一个长度为n的数字串,问可以得到多少种不同的数字串
因为n和m都很小(≤2000),考虑n*m的DP
- dp[x][y]表示只考虑前x行,第x行选中了第y个数字总共能得到多少种不同的数字串
- 很容易想到
, 然后很容易发现dp状态表示没问题,但是这个式子是错的,因为有大量重复
所以这个dp没法直接转移,怎么办?
- 假设能求出
,其中p表示y所在那一段相等的数中,最左边那个数所在的位置,右边一个整体
表示上面一行从p-k 位置开始,到y+k位置结束, 以其中任意一个位置作为最后一个数字,无遗漏不重复的不同数字串个数
- 再求出
,同理,其中q表示y所在那一段相等的数中,最右边那个数所在的位置
到这里可能会问:上面两个等式右边怎么求?等式左边的L[]和R[]求出来的又有什么用?
- 想求出等式右边的部分
, 就必须用到上一行左边的部分L[x][y]和R[x][y]
- 记录
表示x行第y段相等数字中最右边那个数所在位置的L[]值
- 稍加计算得出:
,其中y+k ≥ z ≥ p-k
- 同理
- q[]可以用前缀和O(1)计算,搞定
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<map>
#include<string>
#include<math.h>
#include<queue>
#include<stack>
#include<iostream>
using namespace std;
#define LL long long
#define mod 998244353
char str[2005][2005];
int sum[2005][2005], L[2005][2005], R[2005][2005], bel[2005][2005];
int main(void)
{
int T, n, m, i, j, dis, l, r, cnt, ans;
scanf("%d", &T);
while(T--)
{
scanf("%d%d%d", &n, &m, &dis);
for(i=1;i<=n;i++)
scanf("%s", str[i]+1);
cnt = 0;
for(i=1;i<=m;i++)
{
L[1][i] = R[1][i] = 1;
if(str[1][i]!=str[1][i-1] || i==1)
cnt++;
bel[1][i] = cnt;
sum[1][cnt] = cnt;
}
ans = sum[1][cnt];
for(i=2;i<=n;i++)
{
cnt = 0;
for(j=1;j<=m;j++)
{
if(str[i][j]!=str[i][j-1] || j==1)
l = max(j-dis, 1), cnt++;
bel[i][j] = cnt;
r = min(j+dis, m);
L[i][j] = (L[i-1][r]+R[i-1][l])%mod;
L[i][j] = (L[i][j]+sum[i-1][bel[i-1][r]-1]-sum[i-1][bel[i-1][l]])%mod;
if(L[i][j]<0)
L[i][j] = (L[i][j]+mod)%mod;
sum[i][cnt] = (sum[i][cnt-1]+L[i][j])%mod;
}
ans = sum[i][cnt];
for(j=m;j>=1;j--)
{
if(str[i][j]!=str[i][j+1] || j==m)
r = min(j+dis, r);
l = max(j-dis, 1);
R[i][j] = (L[i-1][r]+R[i-1][l])%mod;
R[i][j] = (R[i][j]+sum[i-1][bel[i-1][r]-1]-sum[i-1][bel[i-1][l]])%mod;
if(R[i][j]<0)
R[i][j] = (R[i][j]+mod)%mod;
}
}
printf("%d\n", ans);
}
return 0;
}
/*
1
3 5 1
11111
11111
11111
*/