4513: [Sdoi2016]储能表
4513: [Sdoi2016]储能表
分析:
数位dp。
横坐标和纵坐标一起数位dp,分别记录当前横纵坐标中这一位是否受n或m的限制,在记录一维表示当前是否已经大于k了。
然后需要两个数组记录答案,分别记录个数和答案的和。
语意不清了。。。看代码吧。。
代码:
#include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<iostream> #include<cctype> #include<set> #include<vector> #include<queue> #include<map> #define fi(s) freopen(s,"r",stdin); #define fo(s) freopen(s,"w",stdout); using namespace std; typedef long long LL; inline LL read() { LL x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 1005; int a[N], b[N], c[N], c1, c2, c3, Cnt; LL dp[N][2][2][2], mi[N], cnt[N][2][2][2], n, m, k, p; #define pa pair<LL,LL> pa dfs(int x,LL now,bool l1,bool l2,bool l3) { if (!x) { return pa((-k + p) % p, 1); } if (dp[x][l3][l1][l2]) return pa(dp[x][l3][l1][l2], cnt[x][l3][l1][l2]); int u1 = l1 ? a[x] : 1; int u2 = l2 ? b[x] : 1; LL res = 0, sum = 0; for (int i = 0; i <= u1; ++i) for (int j = 0; j <= u2; ++j) { int t = i ^ j; if (l3 && t < c[x]) continue; pa tmp = dfs(x - 1, t ? now + mi[x - 1] : now, l1 && i == u1, l2 && j == u2, l3 && t == c[x]); res += (tmp.first + tmp.second * t * mi[x - 1] % p) % p; sum += tmp.second; res %= p; sum %= p; } dp[x][l3][l1][l2]= res, cnt[x][l3][l1][l2] = sum; return pa(res, sum); } void Calc() { n --, m --; c1 = c2 = c3 = Cnt = 0; LL t = n; while (t) a[++c1] = t % 2, t /= 2; t = m; while (t) b[++c2] = t % 2, t /= 2; t = k; while (t) c[++c3] = t % 2, t /= 2; Cnt = max(c1, max(c2, c3)); cout << dfs(Cnt, 0, 1, 1, 1).first << "\n"; for (int i = 0; i <= Cnt; ++i) a[i] = b[i] = c[i] = 0; memset(dp, 0, sizeof(dp)); memset(cnt, 0, sizeof(cnt)); } void solve() { n = read(), m = read(), k = read(), p = read(); mi[0] = 1; for (int i = 1; i <= 100; ++i) mi[i] = mi[i - 1] * 2 % p; Calc(); } int main() { for (int T = read(); T --; solve()); return 0; }