[7.19NOIP模拟测试6]失恋三连(雾 题解

题面(加密)

不得不说这次的题除了引起单身汪极度不适之外还是出的很有水平的……

A.

很好的dp题

模型非常简单,如果数据范围足够友好的话就是一道dp入门题

30%:

我们可以设$dp[i][j]$为到第i天一共喂食给出了j块饼干的方案数

易得转移方程:$dp[i][j+k]=\sum \limits_{k=0}^{min(m-1,n-j)}{dp[i-1][j]}$,i枚举天数,j枚举已给出数量,k枚举下一步给出数量

$\sum \limits_{i=1}^{n}{dp[i][n]}$即为答案

但是i是要枚举到d的,我们的二维数组显然开不了这么大,所以我们可以得到30分的好成绩(滑稽

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=2005;
const ll mod=998244353;
int n,m;
ll dp[N][N],D;
void work()
{
    if(1LL*n>=1LL*m*D||m<=1)
    {
        puts("0");
        return ;
    }
    memset(dp,0,sizeof(dp));
    for(int i=0;i<=min(m-1,n);i++)
        dp[1][i]=1;
    for(int i=2;i<=D;i++)
        for(int j=0;j<min(1LL*n,D*m);j++)
            for(int k=0;k<=min(m-1,n-j);k++)
                (dp[i][j+k]+=dp[i-1][j])%=mod;
    ll ans=0;
    for(int i=1;i<=D;i++)
        (ans+=dp[i][n])%=mod;
    cout<<ans%mod<<endl;
}
int main()
{
    while(1)
    {
        scanf("%d%lld%d",&n,&D,&m);
        if(n==0&&D==0&&m==0)break;
        work();
    }
    return 0;
}
View Code

(另外,冲着30分去就按着30分范围打,不要梦想把数组开到极限还能多得分……事实上博主的30分就因为数组开太大 炸了内存 没了)

100%:

d的范围十分大,但我们不难发现真正给出饼干的天数最多只有n

所以可以把上面那个状态数组的定义稍作更改:真正给出饼干天数为i,给出饼干数量为j时的方案数

另外,为了进一步优化复杂度,使用前缀和优化,我们需要对枚举变量的意义作改动,让等号左侧为$dp[i][j]$

$dp[i][j]=\sum \limits_{k=j-m+1}^{j-1}{dp[i-1][k]}$

在统计结果时,对于每个$dp[i][n]$,是从d天中选任意i天给出饼干,所以还要乘上相应组合数

即:$ans=\sum {dp[i][n]*C_d^i}$

组合数直接根据相邻两项关系递推算即可 记得不能直接除 要乘逆元

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N=2005;
typedef long long ll;
const ll mod=998244353;
ll dp[N][N],n,d,m,fac[N],C[N],sum[N];
ll qpow(ll a,ll b)
{
    ll res=1;
    while(b)
    {
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    //if(res<0)cout<<"Jackpot!"<<endl;
    return res;
}
ll inv(ll x)
{
    return qpow(x,mod-2);
}
void work()
{
    if(n>=m*d||m<=1)
    {
        puts("0");
        return ;
    }
    memset(dp,0,sizeof(dp));
    memset(sum,0,sizeof(sum));
    C[0]=1;
    for(int i=1;i<=min(n,d);i++)
    {
        C[i]=(C[i-1]*(1LL*(d-i+1)%mod))%mod*inv(i)%mod;
/*        if(C[i]<0)while(1);
        cout<<"***"<<C[i]<<endl;*/
    }
    for(int i=1;i<m;i++)
        dp[1][i]=1,(sum[i]=sum[i-1]+dp[1][i])%=mod;
    for(int i=m;i<=n;i++)
        sum[i]=sum[i-1];
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
            dp[i][j]=(sum[j-1]-sum[max(0LL,j-m)]+mod)%mod;
    //    sum[0]=0;
        for(int j=1;j<=n;j++)
            (sum[j]=sum[j-1]+dp[i][j])%=mod;
    }
            
    ll ans=0;
    for(int i=1;i<=min(d,n);i++)
        (ans+=dp[i][n]*C[i]%mod)%=mod;
    cout<<ans<<endl;

}
int main()
{
    while(1)
    {
        scanf("%lld%lld%lld",&n,&d,&m);
        if(!n&&!d&&!m)break;
        work();
    }
    return 0;
}
View Code

B.

数据很水,水到直接把这道题变成了最短路的板子……

枚举与1节点相连的点,对于每个点,把1和他们之间的边临时断开后跑最短路

用$dis[1]+len[i]$更新ans即可

正解似乎是什么二进制分组blablabla  我也不会啊

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#define pa pair<int,int>
using namespace std;
const int N=10005,M=40005;
int n,m,T;
int to[M<<1],dis[N],vis[N],nxt[M<<1],tot,head[N],del[M<<1],len[M<<1];
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*f;
}
void add(int x,int y,int z)
{
    to[++tot]=y;
    len[tot]=z;
    nxt[tot]=head[x];
    head[x]=tot;
}
void dj(int s)
{
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    priority_queue<pa> q;
    dis[s]=0;//vis[s]=1;
    q.push(make_pair(0,s));
    while(!q.empty())
    {
        int x=q.top().second;
        q.pop();
        if(vis[x])continue;
        vis[x]=1;
        for(int i=head[x];i;i=nxt[i])
        {
            if(del[i])continue;
            int y=to[i];
            if(dis[y]>dis[x]+len[i])
            {
                dis[y]=dis[x]+len[i];
                q.push(make_pair(-dis[y],y));
            }
        }
    }
    /*for(int i=1;i<=n;i++)
        cout<<dis[i]<<' ';
    puts(" ");*/
}
void ini()
{
    for(int i=1;i<=n;i++)head[i]=0;
    for(int i=1;i<=m*2;i++)to[i]=nxt[i]=del[i]=len[i]=0;
    tot=1;    
}
void work()
{
    n=read();m=read();
    ini();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read(),z=read();
        add(x,y,z);add(y,x,z);
    }
    int ans=0x3f3f3f3f;;
    for(int i=head[1];i;i=nxt[i])
    {
        int y=to[i];
        del[i]=del[i^1]=1;
        dj(y);
        del[i]=del[i^1]=0;
        ans=min(ans,dis[1]+len[i]);
    }
    cout<<(ans==0x3f3f3f3f?-1:ans)<<endl;
    return ;
}
int main()
{
    T=read();
    while(T--)work();
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/Rorschach-XR/p/11219398.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值