目录
一.题目
二.题解
这道题有点懵呀。容斥原理根本想不到,那么就来想一下DP。
我们定义一个二维DP数组dp[i][j],表示宽度为i,共有j个条形的总情况数。
可以想到有两种情况:
1.这一位二维码与上一位二维码同色,连成一体:dp[i-1][j]
2.这一位二维码与上一位二维码异色,独成一条:dp[i-1][j-1]
但是还有一种特殊情况,就是如果与上一位同色,那么就是如果这一条二维码的长度超过了m,就要减去这种情况:dp[i-1-m][j]
所以状态转移方程就是:
我们求到了总方案数,现在来求要求的那一个方案。
我们先预处理出输入的条形码存入数组,然后循环处理这个数组:
1.如果这一位是1,那么排在这个二维码之前的二维码,就是从这一位开始,前面连起来的1的长度比这个二维码的1连起来的长度短的二维码种数,如:1011100就在1001100的后面
2.如果这一位是0,那么排在这个二维码之前的二维码,就是从这一位开始,后面连起来的0的长度比这个二维码的0连起来的长度长的二维码种数,如:1011001就在1011000的后面
最后答案就出来了。
三.代码
/*
1.求和:
定义dp[i][j]表示宽度为i,共有个条形,每个条形长度不超m
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] - dp[i - m - 1][j - 1];独自占一条+与其他的混搭-每条长度超过m的不合法情况
2.算字典序
运用dp数组,可以发现从同一位置开始的两个条形,在前面的0越多越小,在前面的1越多越大
*/
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define M 40
#define LL long long
int n, k, m, s, len[M];
LL dp[M][M], ans;
char p[M];
void prepare (){//将条形码变为一个k位数的数,如:1001100->1222
int tmp = 0;
for (int i = 1; i <= k; i ++){
if (i % 2 == 1)
while (p[++ tmp] == '1')
len[i] ++;
else
while (p[++ tmp] == '0')
len[i] ++;
tmp --;
}
}
void DP (){//算字典序
int tmp = n;
for (int i = 1; i <= k; i ++){
if (i % 2 == 1)
for (int j = 1; j < len[i]; j ++)
ans += dp[tmp - j][k - i];//这是1的情况,从同一位置开始,如果在前面的1越少,这个条形码的字典序越小
else
for (int j = m; j > len[i]; j --)
if (tmp >= j)
ans += dp[tmp - j][k - i];//这是0的情况,从同一位置开始,如果在前面的0越多,这个条形码的字典序越小
tmp -= len[i];
}
}
int main (){
scanf ("%d %d %d %d", &n, &k, &m, &s);
dp[0][0] = 1;
for (int i = 1; i <= n; i ++){//DP处理总和
for (int j = 1; j <= k; j ++){
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
if (i - 1 >= m)
dp[i][j] -= dp[i - 1 - m][j - 1];
}
}
printf ("%lld\n", dp[n][k]);
for (int i = 1; i <= s; i ++){
memset (len, 0, sizeof(len));
ans = 0;
scanf ("%s", p + 1);
prepare ();
DP ();
printf ("%lld\n", ans);
}
return 0;
}
/*
样例:
7 4 3
5
1001110
1110110
1001100
1001110
1000100
*/