题解 P4067 【[SDOI2016]储能表】
修改了一下,update了一种新做法
来我们接触到这篇题目,我们仔细考虑一下是数位dp。
可以发现可以一位一位的处理,每一位可以从前一位推出,所以可以考虑数位dp。
我们把要统计的数变为二进制表示,先考虑n位二进制的数,再考虑n-1位的数。f[i][1/0][1/0][1/0]为已经考虑到了第i位,第i位是否比n(第i位)小,第i位是否比m小, 是否比k小的总共分数。
g[i][1/0][1/0][1/0]为已经考虑到了第i位,第i位是否比n(第i位)小,第i位是否比m小,是否比k小的所有情况总数。
我们从大到小考虑每一位,我们就可以推出一个式子g[i][aa][bb][cc] += g[i+1][a][b][c];
如果从状态(i,a,b,c)可以转移到状态(i-1, aa, bb, cc);
f[i][aa][bb][cc] += f[i+1][a][b][c] + (zz-z)(1<<i)g[i+1][a][b][c];
前一项不必多说,后一项就是对于每一种可能都可以在前面加上1/0。
最然数据范围是10 18,但是log2(10^18) = 60。所以我们循环60次即可。
代码如下
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#define ll long long
using namespace std;
ll f[62][2][2][2], g[62][2][2][2];
ll n, m, k, T, p;
int main()
{
scanf("%lld", &T);
while (T--) {
memset(f, 0, sizeof(f));
memset(g, 0, sizeof(g));
scanf("%lld %lld %lld %lld", &n, &m, &k, &p);
g[61][1][1][1] = 1;
for (int i = 60; i >= 0; i--) {
int x = (n >> i) & 1, y = (m >> i) & 1, z = (k >> i) & 1;
for (int a = 0; a < 2; a++) {
for (int b = 0; b < 2; b++) {
for (int c = 0; c < 2; c++){
if (f[i + 1][a][b][c] || g[i + 1][a][b] [c]) {
for (int xx = 0; xx < 2; xx++) {
for (int yy = 0; yy < 2; yy++) {
int zz = xx ^ yy;
if ((a && x < xx) || (b && y < yy) || (c && z > zz))
continue;
int aa = (a && x == xx), bb = (b & y == yy),cc = (c && z == zz);
g[i][aa][bb][cc] = (g[i][aa][bb][cc] + g[i + 1][a][b][c]) % p;
f[i][aa][bb][cc] = (f[i][aa][bb][cc] + f[i + 1][a][b][c] + ((zz - z) + p) % p * ((1ll << i) % p) % p * g[i + 1][a][b][c] % p) % p;
}
}
}
}
}
}
}
printf("%lld\n", f[0][0][0][0]);
}
}
分割线---------------------------------------------------------------
这个题可以暴力打表。
初拿此题还是一年前,当时写的数位dp,现在看来可以打表玩。
我们看一下要求的式子:
然后我们定义一个
其实就是想拆个西格玛下来。。。。算是化简吧。。。。。
然后我们在定义一个calc函数。。。美其名曰求题目及若干子问题(好吧我承认就是递归求解)
那么问题来了,这两个函数是怎么来的?
首先我们需要打个表!!!!!
然后我们发现这个表好像是对称的!!!!
这个叫啥对称??对角对称??中心对称再拧回来???总之是对称的就是了。
然后对于
Like this:
然后就可以递归求解了
这里说一下calc的写法。
我们先求出比
如果这个正方形在
如果这个矩形已经超过了
然后我们就可以写个大模拟。。。。。。
然后就可了。
事实证明,什么dp都是浮云,打表暴力才是王道!!!(划死
上代码
#define B cout << "BreakPoint" << endl;
#define O(x) cout << #x << " " << x << endl;
#define O_(x) cout << #x << " " << x << " ";
#define Msz(x) cout << "Sizeof " << #x << " " << sizeof(x)/1024/1024 << " MB" << endl;
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#define LL long long
#define inf 1000000009
using namespace std;
inline LL read() {
LL s = 0,w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-')
w = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * w;
}
LL T,N,M,K,p,tmp;
LL sum(LL x,LL y) {
if(x < 0) {
x = 0;
}
if(y < 0) {
y = 0;
}
LL res = y - x + 1;
if(res & 1) {
return (res % p) * ((x + y >> 1) % p) % p;
} else {
return ((res + 1 >> 1) % p) * ((x + y) % p) % p;
}
}
LL calc(LL n,LL m,LL k) {
if(n < 0||m < 0) {
return 0;
}
if(n == 0 && m == 0) {
return max(((N - 1) ^ (M - 1)) - K,tmp) % p;
}
if(n < m) {
swap(n,m);
}
LL l = 0,t;
while((1LL << l) <= n + 1) l++;
l--;
t = (1LL << l) - 1;
if(t <= m) {
return ((t + 1) % p * sum(-k,t - k) % p + (n - t + m - t) % p * sum(t + 1 - k,t + t + 1 - k) % p + calc(n - t - 1,m - t - 1,k)) % p;
} else {
return ((m + 1) % p * sum(max(-k,tmp),max(tmp,t - k)) % p + calc(n - t - 1,m,k - t - 1)) % p;
}
}
void init() {
N = read(),M = read(),K = read(),p = read();
LL now = calc(N - 1,M - 1,K);
printf("%lld\n",now);
return ;
}
void solve() {
T = read();
while(T--){
init();
}
return ;
}
int main(){
solve();
return 0;
}