3848. 【NOIP2014八校联考第1场第2试9.21】大水题(water) (Standard IO)

本文解析了一道复杂的数位动态规划(DP)竞赛题目,介绍了如何通过增加状态数来解决此类问题。详细阐述了使用DP求解生成矩阵中所有小于等于特定数的组合数量,包括状态定义、转移方程及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Time Limits: 1000 ms  Memory Limits: 524288 KB  Detailed Limits  

Description

dzy 定义一个n^2 位的数的生成矩阵A 为一个大小为n*n 且Aij 为这个数的第i*n+j-n位的矩阵。
现在dzy 有一个数n^2 位的数k,他想知道所有小于等于k 的数的n*n 生成矩阵有多少种。(如果不足n^2 位则补前缀零)

Input

第一行一个数n,第二行一个n^2 位的数k

Output

仅一行表示答案,答案可能很大,你只需输出答案对10^9 + 7 取模后的结果。

Sample Input

2
1000

Sample Output

954

Data Constraint

对于30% 的数据n<=2
对于100% 的数据n <=1000,且n为偶数

Hint

【提示】
    如果两个生成矩阵在其中一个旋转180 度后可以重叠,则称这两个矩阵是相同的。

总结:

比赛的时候只打了暴力,像这种数位dp, 从来没见过。有时候能增加状态数解决dp。

 

题解:

数位dp。

对于后面的分子, 我们可以dp求出。

f[i][j][k]表示构造到第i位, j = 0 / 1, k = 0 / 1

j 等于1表示 构造的数组前i位和a数组相等, k等于1 表示前i位翻转后 <= a最后的i位

 

转移 : 

f[i][j][k] - > f[i + 1][a][b]

a b 是未知的, 我们尝试枚举i + 1上我们构造的数字, 设为l。

转移的条件 1:第i位前就小于a[], 或者前面等于, 这一位小于等于。

a 为 1的条件 前面就小于, 或这一位小于。

b为1的条件 (注意, 前i位要翻转了, 故越后面的数位比较优先级越高) : 之前相等吗现在<= 或 当前数 < a[n - i]

 

转移时注意 :

  1. 构造的数组时刻小于等于a, 若大于了, 对答案毫无影响, 算他干嘛。
  2. f[i][j][k] - > f[i +1][a][b]的过程中, 若f[i][j][k] = 0, 对答案也毫无影响。

 

答案:

我们分别算出s1, s2.

s1

要求双重 <=k

当构造的数组等于a时, f[n][0][1]

当<时, f[1][1]

 

s2

f[n/2]

当为0时, 前面等于后面只能<=

当为1时, 后面随意。

 

初始化f[0][0][1] = 1

#include<bits/stdc++.h>
#define open(x) freopen(x".in", "r", stdin);freopen(x".out", "w", stdout)
#define mem(a, b) memset(a, b, sizeof(a))
#define mcy(a, b) memcpy(a, b, sizeof(a))
#define pf printf
#define sf scanf
#define fo(i, a, b) for( ll i = a; i <= b; ++i)
#define fown(i, a, b) for( ll i = a; i >= b; --i)
#define em(p, x) for(ll p=tail[x];p;p=e[p].fr)
#define ll long long
#define N 1010
#define maxn N * N
#define mod (ll)(1e9 + 7)
using namespace std;

template<class T>
T in(T &x) {
	x=0;
	char ch = getchar();
	ll f = 0;
	while(ch < '0' || ch > '9') f |= ch == '-', ch = getchar();
	while(ch >= '0' && ch <= '9') x = (x<<1) + (x<<3) + ch - '0', ch = getchar();
	x = f ? -x : x;
	return x;
}

ll n, k, ans;
ll f[maxn][2][2], a[maxn];

ll sqr(ll x) {return x * x% mod;}

ll power(ll x, ll k) {
	if(k == 0 ) return 1;
	if(k & 1) return x * sqr(power(x, k / 2)) % mod; 
	return sqr(power(x, k / 2));
}


int main() {
	open("water");
	sf("%lld\n", &n);
	n = n * n;
	char ch;
	fo(i, 1, n) {
		sf("%c", &ch);
		ans = (ans * 10 % mod + ch - '0') % mod;
		a[i] = ch - '0';
	}
	sf("\n");
	f[0][0][1] = 1;
	fo(i, 0, n - 1)
	  fo(j, 0, 1)
	    fo(k, 0, 1) if(f[i][j][k])
		  fo(l, 0, 9)
		if(j || a[i + 1] >= l) {
			ll b, c;
			b = j || (l < a[i + 1]);
			c = (k && a[n - i] == l) || (a[n - i] > l);
			(f[i + 1][b][c] += f[i][j][k]) %= mod;
		}
	ll s1 = 0, s2 = 0;
	(s1 = f[n][0][1] + f[n][1][1])%=mod;
	(s2 = (f[n/2][1][0] + f[n/2][1][1])% mod+ f[n/2][0][1])%=mod;
	ans = ans - ((s1 - s2) % mod + mod) % mod * power(2, mod - 2) % mod;
	(ans += mod) %= mod;
	pf("%lld\n", ans);
	return 0;
}

O(n^2)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值