ysj的模拟赛

链接:https://www.luogu.org/problemnew/show/P4917

天守阁的地板

题目描述

为了使万宝槌能发挥出全部魔力,小碗会将买来的地板铺满一个任意边长的正方形(地板有图案,因此不允许旋转,当然,地板不允许重叠)来达到最大共鸣

现在,她能够买到规格为a∗ba*bab的地板,为了省钱,她会购买尽可能数量少的地板

现在,她想知道对于每一对a,b(1≤a,b≤n)a,b(1≤a,b≤n)a,b(1a,bn),她最少需要购买的地板数量

由于输出可能很大,所以你只需要输出所有答案的乘积即可,为了避免高精度,小碗很良心的让你将答案对192608171926081719260817取模

输入输出格式

输入格式:

 

第一行一个整数TTT,表示数据组数
下面TTT行,每行一个整数nnn

 

输出格式:

 

TTT行,每行一个整数,表示取模后的答案

 

输入输出样例

输入样例#1:  复制
4
1
2
3
100
输出样例#1:  复制
1
4
1296
18996121

说明

样例解释:
对于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=41221=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);
    }
}
View Code

 

4918 信仰收集

输入样例#1:  复制
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
输出样例#1:  复制
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);
    
}
View Code

 

转载于:https://www.cnblogs.com/EdSheeran/p/9757297.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值