NOI2018

44 篇文章 0 订阅
19 篇文章 0 订阅

NOI2018

夕阳红选手就会做俩签到题,剩下的日后再补。

归程(UOJ5415,LOJ2718)

题目描述
uoj:https://www.lydsy.com/JudgeOnline/problem.php?id=5415
log:https://loj.ac/problem/2718

题解
看到这题先yy了一个傻逼做法,吧所有边按海拔从大到小跑一遍可持久化并查集,并且维护个距离最下值。在询问的时候在对应的点找到对应的可持久化并查集版本查询就好了。
后来发现了一种更优秀的做法,我们把这些边按照海拔从大到小建一课kruskal重构树,每个点维护下子树的最大值。每次询问等于查询一个子树的最大值,先倍增一下再求个最大值就好了。
//现在OI题是都会卡spfa的吗,改了dij才过的。

代码

#include<bits/stdc++.h>
using namespace std;
#define N 400010
#define D 19
using namespace std;
int T,n,m,pre,Q,K,S,fa[N],f[N],g[N];
int k,la[N],ff[N*2],w[N][D+1],cnt;
struct node{
  int a,b,c,d;
  bool operator<(const node &p)const{return d>p.d;}
}e[N*2];
struct info{
  int a,b;
  bool operator<(const info &p)const{return b>p.b;}
};
priority_queue<info>q;
void add(int a,int b,int c,int d)
{
  e[++k]=(node){a,b,c,d};ff[k]=la[a];la[a]=k;
  e[++k]=(node){b,a,c,d};ff[k]=la[b];la[b]=k;
}
inline int read()
{
  char ch;int v;
  while(!isdigit(ch=getchar()));v=ch-48;
  while(isdigit(ch=getchar()))v=v*10+ch-48;
  return v;
}

int find(int x)
{
  if(fa[x]==x)return x;
  return fa[x]=find(fa[x]);
}

inline void dij()
{
  memset(f,63,sizeof(f));
  q.push((info){1,f[1]=0});
  while(!q.empty())
  {
    info p=q.top();q.pop();
    if(f[p.a]!=p.b)continue;
    int x=p.a;
    for(int a=la[x];a;a=ff[a])
      if(f[e[a].b]>f[x]+e[a].c)
      {
        f[e[a].b]=f[x]+e[a].c;
        q.push((info){e[a].b,f[e[a].b]});
      }
  }
}

void prepare()
{
  w[cnt][0]=cnt;
  for(int j=1;j<=D;j++)
    for(int i=1;i<=cnt;i++)
      w[i][j]=w[w[i][j-1]][j-1];
}


int solve(int x,int y)
{
  for(int i=D;i>=0;i--)
    if(g[w[x][i]]>y)x=w[x][i];
  return x;
}

int main()
{
  int a,b,c,d;
  T=read();
  while(T--)
  {
    n=read();m=read();cnt=n;k=0;pre=0;
    for(int i=1;i<=n;i++)fa[i]=i;
    memset(la,0,sizeof(la));
    memset(ff,0,sizeof(ff));
    memset(w,0,sizeof(w));
    memset(g,0,sizeof(g));
    for(int i=1;i<=m;i++)
      a=read(),b=read(),c=read(),d=read(),add(a,b,c,d);
    dij();sort(e+1,e+m*2+1);
    for(int i=1;i<=m*2;++i)
    {
      a=find(e[i].a);b=find(e[i].b);
      if(a==b)continue;
      w[a][0]=w[b][0]=++cnt;fa[a]=cnt;fa[b]=cnt;
      fa[cnt]=cnt;g[cnt]=e[i].d;f[cnt]=min(f[a],f[b]);
    }
    prepare();
    Q=read();K=read();S=read();
    while(Q--)
    {
      a=read();b=read();
      a=(a+K*pre-1)%n+1;b=(b+K*pre)%(S+1);
      printf("%d\n",pre=f[solve(a,b)]);
    }
  }
  return 0;
}

屠龙勇士(UOJ5418,LOJ2721)

题目描述
uoj:https://www.lydsy.com/JudgeOnline/problem.php?id=5418
log:https://loj.ac/problem/2721

题解
先用set把每次攻击对应的剑找到,然后这题就变成了给你n个同余方程求解。
大概就是 t[i]*x==a[i](mod p[i])
如果模数两两互质的话直接上CRT就好了,不互质的话就先每个用exgcd求解然后再两两合并。
至于合并,考虑前n个方程得到的解 x==X(mod D),即x=k1*D+X。
当前同余方程的解,x=k2*d+x0,所以 k1*D+X=k2*d+x0,移个项就是同余方程,上exgcd搞搞就可以了。
无解的话就是任意一个同余方程无解。

代码

#include<bits/stdc++.h>
#define ll long long
#define N 100010
using namespace std;
int T,n,m;ll a[N],p[N],s[N],t[N];
void exgcd(ll a,ll b,ll &x,ll &y)  
{
  if(!b){x=1;y=0;return;}  
  exgcd(b,a%b,y,x);y-=x*(a/b);
}
multiset<ll>q;
multiset<ll>::iterator it;
inline ll mul(ll a,ll b,ll n)
{
  ll res=0;a%=n;
  while(b)
  {
    if(b&1)res=(res+a)%n;
    a=(a<<1)%n;b/=2;
  }
  return (res+n)%n;
}

int main()
{
  //freopen("dragon.in","r",stdin);
  //freopen("dragon.out","w",stdout);
  ll x;
  scanf("%d",&T);
  while(T--)
  {
    scanf("%d%d",&n,&m);q.clear();
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++)scanf("%lld",&p[i]);
    for(int i=1;i<=n;i++)scanf("%lld",&s[i]);
    for(int i=1;i<=m;i++)scanf("%lld",&x),q.insert(x);
    for(int i=1;i<=n;i++)
    {
      it=q.upper_bound(a[i]);
      if(it!=q.begin())--it;
      t[i]=*it;q.erase(it);q.insert(s[i]);
    }
    //同余方程:t[i]*x==a[i](mod p[i]) 
    int flag=0;ll X,D;
    for(int i=1;i<=n;i++)
    {
      ll tmp=__gcd(t[i],p[i]),x0,y0,d,x,y;
      if(a[i]%tmp){flag=1;break;}
      exgcd(t[i]/tmp,d=p[i]/tmp,x0,y0);
      x0=mul(x0,a[i]/tmp,d);if(!x0)x0=d;
      y0=(a[i]-t[i]*x0)/p[i];
      if(y0>0)
      {
        ll g=t[i]/tmp;
        ll bs=((g+(-y0)%g)%g+y0)/g;
        x0+=bs*d;
      }
      //解同余方程 当前方程解 x=x0+k*d 
      if(i==1){X=x0;D=d;continue;}
      if(x0<X)swap(x0,X),swap(D,d);
      tmp=__gcd(D,d);
      if((x0-X)%tmp){flag=1;break;}
      exgcd(D/tmp,d/tmp,x,y);
      x=mul(x,(x0-X)/tmp,d/tmp);y=(x0-X-D*x)/d;
      if(y>0)
      {
        ll g=D/tmp;
        ll num=((-y)%g+g)%g;
        ll bs=(num+y)/tmp;
        x+=bs*d/tmp;y=num;
      }
      X=x0-d*y;D=D/tmp*d;
      //合并同于方程 得到解 x=X+k*D 
    }
    if(flag)printf("-1\n"); 
    else printf("%lld\n",X);
  }
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值