链接:https://www.luogu.org/problemnew/show/P4917
天守阁的地板
题目描述
为了使万宝槌能发挥出全部魔力,小碗会将买来的地板铺满一个任意边长的正方形(地板有图案,因此不允许旋转,当然,地板不允许重叠)来达到最大共鸣
现在,她能够买到规格为a∗ba*ba∗b的地板,为了省钱,她会购买尽可能数量少的地板
现在,她想知道对于每一对a,b(1≤a,b≤n)a,b(1≤a,b≤n)a,b(1≤a,b≤n),她最少需要购买的地板数量
由于输出可能很大,所以你只需要输出所有答案的乘积即可,为了避免高精度,小碗很良心的让你将答案对192608171926081719260817取模
输入输出格式
输入格式:
第一行一个整数TTT,表示数据组数
下面TTT行,每行一个整数nnn
输出格式:
共TTT行,每行一个整数,表示取模后的答案
输入输出样例
说明
样例解释:
对于n=1,a,b仅有(1,1)(1,1)(1,1一种情况,只需要一块1∗1的地板,答案为1
对于n=2,a,b有(1,1),(1,2),(2,1),(2,2)四种情况,分别需要一块(1∗1)两块(1∗2),两块(2∗1),一块(2∗2)的地板,答案为1∗2∗2∗1=41*2*2*1=41∗2∗2∗1=4
追加解释:a,b有四种取值,分别是(1,1),(1,2),(2,1),(2,2)
当只能买到1∗1的地板时,需要一块(本身就是正方形)
当只能买到1∗2的地板时,需要两块(两块拼在一起组成2∗2的正方形)
当只能买到2∗1的地板时,需要两块(两块拼在一起组成2∗2的正方形)
当只能买到2∗2的地板时,需要一块(本身就是正方形)
答案就是这些数的乘积,即444
T<=100, n <=1e6
题解:一道非常好的数论题
主要涉及了两个知识点(都做过,但并没有想到结合):
第一个,求gcd(x, y) = d, x,y <= n 的对数,详见:https://www.cnblogs.com/EdSheeran/p/9329517.html
第二个,乘除分块,详见:https://www.cnblogs.com/EdSheeran/p/9470009.html
#include<bits/stdc++.h> using namespace std; #define LL long long const int M = 1e6 + 5; const LL mod = 19260817; LL sum[M],fac[M]; int vfac[mod + 1]; bool vis[M]; int phi[M], tot, prime[M]; LL ksm(LL a, LL b){ LL ret = 1; for(;b;b>>=1,a=a*a%mod) if(b&1)ret=ret*a%mod; return ret; } void pre(){ fac[0] = vfac[0] = vfac[1] = 1; for(int i = 1; i < M; i++) fac[i] = fac[i - 1] * i % mod; for(int i = 2; i < mod; i++) vfac[i] = (-(LL)(mod/i) * vfac[mod%i] % mod + mod) % mod; phi[1] = 1; for(int i = 2; i < M; i++){ if(!vis[i]) phi[i] = i - 1, prime[++tot] = i; for(int j = 1; j <= tot && (LL)i * prime[j] < M; j++){ int m = i * prime[j]; vis[m] = 1; if(i % prime[j] == 0){// if n%i == 0 phi[n*i] = phi[n] * i; phi[m] = phi[i] * prime[j]; break; } phi[m] = phi[i] * phi[prime[j]]; } } for(int i = 1; i < M; i++)sum[i] = sum[i - 1] + phi[i]; } int main(){ int T; scanf("%d", &T); pre(); while(T--){ int n; scanf("%d", &n); LL ans1 = ksm(fac[n], 2*n); LL ans2 = 1; for(int i = 1, rg; i <= n;){ rg = n/(n/i); LL p = sum[n/i]; ans2 = ans2* ksm(fac[rg]*(LL)vfac[fac[i-1]]%mod, 2*p-1) % mod; i = rg + 1; } ans2=ans2*ans2%mod; ans1 = ans1 * (LL)vfac[ans2]%mod; printf("%lld\n", ans1); } }
4918 信仰收集
3 7 8 1 2 3 2 2 2 4 3 6 4 1 2 2 4 4 5 2 6 7 6 6 4 3 2 3 4
2
题解:dp[u][j]表示当前在u号节点,这段瞬移还有j才结束的最大收益;
虽然dp方程我想出来了,但是实际操作我遇到了问题;
一是怎么确定遍历顺序,我做了一遍SPFA,从距离大的倒着更新;(这应该是有问题的,而且常数大)
二是我瞬移的花费与收获在出发时还是结束时计算;
看了std,第一个问题可以用拓扑排序解决,通过入度更新,这样是显然正确的;
第二个是在出发时减去花费,到达时加上当前点的贡献,不重不漏;
#include<bits/stdc++.h> #define ll long long using namespace std; const int M = 2e5 + 10, ME = 3e5 + 10, inf = -2e9; int dp[M][52], d[2], w[2], here[M], deg[M]; queue <int> q; struct edge{int v, nxt;}G[ME]; int tot,cnt,dis[M],hh[M],h[M],a[M]; bool inq[M]; void add(int u, int v){ G[++tot].v=v,G[tot].nxt=h[u],h[u]=tot; } int read(){ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} return x*=f; } #define ex(i, u) for(int i = h[u]; i; i = G[i].nxt) void del(int u){ ex(i, u){ int v = G[i].v; deg[v]--; if(!deg[v] && v!=1)q.push(v); } } int main(){ int u, v, n, m, k; scanf("%d%d%d", &n, &m, &k); scanf("%d%d%d%d", &d[0], &d[1], &w[0], &w[1]); for(int i = 1; i <= n; i++){ u = read(), v = read(); here[u] += v; } for(int i = 1; i <= k; i++){ u = read(), v = read(); add(u, v); deg[v]++; } for(int i = 2; i <= m; i++) if(!deg[i])q.push(i); while(!q.empty()){ int u=q.front();q.pop(); del(u); }// the useless vertex q.push(1); memset(dp, 0x8f, sizeof(dp)); dp[1][0] = here[1]; while(!q.empty()){ int u=q.front();q.pop(); del(u); ex(i, u){ int v=G[i].v; if(dp[u][0] > inf){ if(d[0]!=1) dp[v][d[0]-1] = max(dp[v][d[0]-1], dp[u][0] - w[0]); else dp[v][0] = max(dp[v][0], dp[u][0] - w[0] + here[v]); if(d[1]!=1) dp[v][d[1]-1] = max(dp[v][d[1]-1], dp[u][0] - w[1]); else dp[v][0] = max(dp[v][0], dp[u][0] - w[1] + here[v]); } if(dp[u][1] > inf)dp[v][0] = max(dp[v][0], dp[u][1] + here[v]); for(int j=1; j<d[1]; j++){ dp[v][j] = max(dp[v][j], dp[u][j+1]); } } } int ans = 0; for(int i = 1; i <= m; i++) ans = max(ans, dp[i][0]); printf("%d\n", ans); }