暑假集训日记——7.29(牛客+数论)

DongDong坐飞机
题解:分层图最短路,dis[ i ][ j ]记录下到达 i点 j次打折的最短路
然后Dijkstra就可解了。
注意:
1.因为是分层的所以,一个节点会多次遍历所以不必标记
2.输入输出用快读,或者scanf,居然显示的数据错误,而不是超时…卡在80%

AC代码

#include<bits/stdc++.h>
#include<queue>
#define int long long
#define INF 1e18
using namespace std;

const int maxn=20010;
typedef long long ll;

inline int read(){
    int w=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        w=(w<<3)+(w<<1)+ch-48;
        ch=getchar();
    }
    return w*f;
}

ll dis[maxn][12],value[maxn];
int vis[maxn];//不需要记录是否询问,因为需要重复访问

struct edge
{
    ll from,to,val,pen;
}h;

struct cmp{
    bool operator()(const edge &a,const edge &b)
    {
            return a.val>b.val;
    }
};

signed main(){
    ll m,n,s,t,k;
    m=read();n=read();k=read();
    vector<edge>E[maxn];
    priority_queue<edge,vector<edge>,cmp>Q;
        for(int i=0;i<=20000;i++)
            for(int j=0;j<=10;j++)
                dis[i][j]=INF;

    for(int i=0;i<n;i++)
        {
            ll u,v,w,p;
            u=read(),v=read(),w=read();
            h.from=u,h.to=v,h.val=w;
            E[u].push_back(h);
        }

        s=1,t=m;
        dis[s][0]=0;
        h.from=s,h.to=s,h.val=0,h.pen=0;
        Q.push(h);
        while(!Q.empty())
        {
            edge x=Q.top();
            Q.pop();
            if(x.pen>k) continue;
            if(x.val>dis[x.to][x.pen]) continue;
            int sz=E[x.to].size();
            for(int i=0;i<sz;i++)
            {
                edge y=E[x.to][i];
                if(dis[y.to][x.pen]>dis[x.to][x.pen]+y.val)
                {
                    dis[y.to][x.pen]=dis[x.to][x.pen]+y.val;
                    h.from=x.to,h.to=y.to,h.val=dis[y.to][x.pen],h.pen=x.pen;
                    Q.push(h);
                }
                if(dis[y.to][x.pen+1]>dis[x.to][x.pen]+y.val/2)
                {
                    dis[y.to][x.pen+1]=dis[x.to][x.pen]+y.val/2;
                    h.from=x.to,h.to=y.to,h.val=dis[y.to][x.pen+1],h.pen=x.pen+1;
                    Q.push(h);
                }
            }
        }
        ll ans=INF;
        for(int i=0;i<=k;i++){
            //cout<<dis[t][i]<<endl;
            ans=min(ans,dis[t][i]);
        }
        if(ans==INF) cout<<-1<<endl;
        else cout<<ans<<endl;
}

DongDong数颜色
题解:
做法1:dfs序+莫队,先把树上的点标上dfs序,因为子树的dfs序是连续的,所以子树可以表示为id[x]到id[x]+size[x]+1id[x]到id[x]+size[x]+1,然后就是序列上莫队了
做法2:DSU ON TREE,对于每个节点的子树开一颗线段树或者set,然后不断向上合并即可
做法3:考虑每个节点的贡献,点u对上一个颜色与点u相同的点的LCA贡献-1,u对自身贡献1,每个节点的ans就是子树贡献和,然后上个树状数组或者线段树维护一下就好。

莫队+dfs序 代码:

#include <bits/stdc++.h>
using namespace std;
 
const int maxn=100000+10;
int inx[maxn];
int out[maxn];
int n,k;
int color[maxn];
int c[maxn];
vector<int>G[maxn];
int cnt;
int vis[maxn];

void dfs(int u){///dfs序
    inx[u]=++cnt;
    vis[u]=1;
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        if(!vis[v])
            dfs(v);
    }
    out[u]=cnt;
}
//莫队离线询问
struct node{
    int l,r,id;
}Q[maxn];
 
int pos[maxn];//保存所在块

bool cmp(const node &a,const node &b)
{
    if(pos[a.l]==pos[b.l])
        return a.r<b.r;
    return pos[a.l]<pos[b.l];
}

int flag[maxn];//数字在当前区间出现次数
int Ans;
int ans[maxn];

void add(int x){ flag[c[x]]++; if(flag[c[x]]==1) Ans++;}
 
void del(int x){ flag[c[x]]--; if(flag[c[x]]==0) Ans--;}


int main(){
    scanf("%d%d",&n,&k);
    cnt=0;
    int sz=sqrt(n);
    for(int i=1;i<=n;i++){
        scanf("%d",&color[i]);
        pos[i]=i/sz;
    }
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    memset(vis,0,sizeof(vis));
    memset(out,0,sizeof(out));
    memset(inx,0,sizeof(inx));
    memset(flag,0,sizeof(flag));
    ///以上都是初始化
    dfs(1);
    for(int i=1;i<=n;i++){
        c[inx[i]]=color[i];
    }
    Ans=0;
    for(int i=1;i<=k;i++){
        int u;
        scanf("%d",&u);
        int ll=inx[u],rr=out[u];
        Q[i].l=ll;
        Q[i].r=rr;
        Q[i].id=i;
    }
    sort(Q+1,Q+k+1,cmp);///莫队排序
    int L=1,R=0;
    for(int i=1;i<=k;i++)
    {
        while(R<Q[i].r){R++;add(R);}
        while(L>Q[i].l){L--;add(L);}
        while(L<Q[i].l){del(L);L++;}
        while(R>Q[i].r){del(R);R--;}
        ans[Q[i].id]=Ans;
    }
    for(int i=1;i<=k;i++)
        printf("%d\n",ans[i]);
    return 0;
}

D. GCD Table
题意:
给定n*m的矩阵,[i,j]的点值为gcd(i,j)
给定一个k长的序列,问是否能匹配上 矩阵的某一行的连续k个元素
题解:中国剩余定理
注意:数字比较大,需要用快速乘优化一下

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const int N=10005;

LL k;
LL c[N],m[N],mp[N];///mp为m的副本
bool flag;

LL mul(LL a,LL p,LL Mod)///快速乘
{
    LL ans=0LL,f;
    if (p<0) a=-a,p=-p;
    for (;p;p>>=1LL,a=(a+a)%Mod)
        if (p&1LL)
            ans=(ans+a)%Mod;
    return ans;
}

LL gcd(LL a,LL b)
{
    return b?gcd(b,a%b):a;
}

LL exgcd(LL a,LL b,LL &x,LL &y)
{
    if(!b) {x=1;y=0;return a;}
    else exgcd(b,a%b,y,x),y-=a/b*x;
}

LL inv(LL a,LL mod)
{
    LL x,y;
    exgcd(a,mod,x,y);
    return (x%mod+mod)%mod;
}

LL exCRT(LL k)///中国剩余定理
{
    LL m1,m2,c1,c2,g;
    for(LL i=2;i<=k;i++)
    {
        m1=m[i-1],m2=m[i];
        c1=c[i-1],c2=c[i];
        g=gcd(m1,m2);
        if((c2-c1)%g) {flag=0;return -1;}
        m[i]=m1*m2/g;
        ///c[i]=inv(m1/g,m2/g)*((c2-c1)/g)%(m2/g)*m1+c1;
        ///数字太大乘法超long long 应用大数乘法
        c[i]=inv(m1/g,m2/g);
        c[i]=mul(c[i],((c2-c1)/g),(m2/g));
        c[i]=mul(c[i],m1,m[i])+c1;
        c[i]=(c[i]%m[i]+m[i])%m[i];
    }
    return c[k];
}

int main()
{
    LL n,m1;
    scanf("%lld%lld%lld",&n,&m1,&k);
    LL lcm=1;
    for(LL i=1;i<=k;i++) scanf("%lld",&m[i]),c[i]=m[i]-i+1LL,mp[i]=m[i];
    lcm=m[1];
    for (LL i=2;i<=k;++i)
    {
        LL t=__gcd(lcm,m[i]);
        lcm=lcm/t*m[i];
        if (lcm>n) {printf("NO\n");return 0;}
    }
    LL ans=exCRT(k);
    //cout<<ans<<endl;
    if(ans==0) ans+=m[k];///找到不为0的最小符合题意的数,因为数的范围为[1,
    if(ans+k-1>m1 || ans<0){printf("NO\n");return 0;}
    for(LL i=1;i<=k;i++)
    {
        //cout<<__gcd(lcm,ans+i-1)<<" "<<m[i]<<endl;
        if(__gcd(lcm,ans+i-1LL)!=mp[i]){printf("NO\n");return 0;}
    }
    printf("YES\n");

}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值