NOIP2017题解

T1小凯的疑惑

小凯手中有两种面值的金币,两种面值均为正整数且彼此互素。每种金币小凯都有 无数个。在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的。现在小 凯想知道在无法准确支付的物品中,最贵的价值是多少金币?注意:输入数据保证存在 小凯无法准确支付的商品。

Solution

2017数论题,感觉是历史以来最难的一道。

对于ax+by=c(a>=0,b>=0)当方程无解是,必定为x<0&&y>0且下一组解x>0&&y<0那x为-1,y为a-1,那c就是a*b-a-b。

Code

#include<iostream>
#include<algorithm>
using namespace std;
  long long a,b;
int main()
  {
      cin>>a>>b;
      cout<<a*b-a-b;
  }
  

T2时间复杂度

题面太长不粘了

模拟题,注意细节。。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int t,vis[1009],tag,tag2,n,ans,num,tot,s[109],st[109],top,maxx=10000000,yy,zz;
char c,cc[109],x,y[10],z[10];
int main()
{
    cin>>t;
    while(t--)
    {
        memset(vis,0,sizeof(vis));
        scanf("%d",&n);
        tag=tag2=tot=num=ans=top=0;
        cin>>cc;        
        int p=strlen(cc);
             if(cc[2]=='n')
               for(int j=4;j<=p;++j)
                  if(cc[j]>='0'&&cc[j]<='9') num=num*10+cc[j]-'0';
                  else break;   
        for(int i=1;i<=n;++i)
          {
              cin>>c;
              if(c=='F')
              {
                  cin>>x>>y>>z;
                yy=zz=0;
                  if(vis[x])tag=1;
                  vis[x]=1;
                  s[++top]=x;
                  if(y[0]!='n')
                  {
                      int o=strlen(y);
                     for(int k=0;k<o;++k)
                     yy=yy*10+y[k]-'0';
                   }
                  else yy=maxx;
                  if(z[0]!='n')
                  {
                      int o=strlen(z);
                     for(int k=0;k<o;++k)
                     zz=zz*10+z[k]-'0';
                }
                  else zz=maxx;
                  if(yy<maxx&&zz==maxx)
                  {
                      st[top]=1;
                      tot++;
                  }
                  else
                  if(yy>zz)
                  {
                      st[top]=-1;
                      tag2++;
                  }
                  else
                  {
                      st[top]=0;
                  }
                  if(!tag2)ans=max(ans,tot);
              }
              else
              {
                  if(!top)tag=1;    
                  if(st[top]==1)tot--;
                  else if(st[top]==-1)tag2--;
                  vis[s[top]]=0;
                  top--;
              }
          }
        if(top||tag)
        {
            cout<<"ERR
";
            continue;
        }
        if(ans==num)cout<<"Yes
";
        else cout<<"No
";
    }
    return 0;
}

T3逛公园

T4奶酪

现有一块大奶酪,它的高度为 hh,它的长度和宽度我们可以认为是无限大的,奶酪 中间有许多 半径相同 的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中, 奶酪的下表面为z = 0z=0,奶酪的上表面为z = hz=h。

现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐 标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别 地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果 一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。

位于奶酪下表面的 Jerry 想知道,在 不破坏奶酪 的情况下,能否利用已有的空洞跑 到奶酪的上表面去?

Solution

水题 ,可以把上界和下界都看成一个点,n^2并查集搞一下就可以了。

听说这题爆longlong,反正我开double直接开根,精度没什么问题

Code

#include<iostream>
#include<cstdio>
#include<cmath>
#define N 1012
using namespace std;
int n,t,f[N];
double h,r;
struct ssd{
    double x,y,z;
}a[N];
double calc(int i,int j){
    return sqrt((a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y)+(a[i].z-a[j].z)*(a[i].z-a[j].z));
}
int find(int x){return f[x]=f[x]==x?x:find(f[x]);} 
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%lf%lf",&n,&h,&r);
        for(int i=0;i<=n+1;++i)f[i]=i;
        for(int i=1;i<=n;++i){
         scanf("%lf%lf%lf",&a[i].x,&a[i].y,&a[i].z);
         for(int j=1;j<i;++j)
           if(calc(i,j)<=r*2){
               int xx=find(i),yy=find(j);
               if(xx!=yy)f[xx]=yy;
           }
           if(a[i].z-r<=0){
                 int xx=find(i),yy=find(0);
                 if(xx!=yy)f[xx]=yy;
           }
           if(a[i].z+r>=h){
               int xx=find(i),yy=find(n+1);
               if(xx!=yy)f[xx]=yy;
           }
        }
        if(find(0)==find(n+1))printf("Yes
");
        else printf("No
");
    }
    return 0;
} 

T5宝藏

如果我们设dp[s]表示当前选择集合为s时的最小代价,这个状态是有问题的,说不好听点它压根就是错的,因为它压根就没有考虑每个节点的深度。

我们可以这么考虑问题,我们以深度作为阶段,每次从当前选择集合中扩展一些点作为第i层。

这时我们就可以设计状态了,令dp[i][s]表示当前深度做到了i,当前选择集合为s时的最优方案。

转移时可以枚举扩展出哪些点,然后直接转移。

这样的复杂度是3^n*n^2。

但这样太慢,可以利用lowbit等技巧优化常数

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 13
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
int dp[N][1<<N],n,m,x,y,l,e[N][N],ji[1<<N],f[1<<N],b[N],tag[N],be[1<<N],ans;
inline int rd(){
    int x=0;char c=getchar();
    while(!isdigit(c))c=getchar();
    while(isdigit(c)){
        x=(x<<1)+(x<<3)+(c^48);c=getchar();
    }
    return x;
}
int main(){
    n=rd();m=rd();
    memset(e,0x3f,sizeof(e));
    for(int i=1;i<=m;++i){
        x=rd();y=rd();l=rd();
        e[x][y]=e[y][x]=min(e[x][y],l);
    }
    memset(dp,0x3f,sizeof(dp));
    for(int i=0;i<n;++i)ji[1<<i]=i,dp[0][1<<i]=0;ans=inf;
    ans=min(ans,dp[0][(1<<n)-1]);
    for(int i=1;i<=n;++i)
      for(int s=1;s<(1<<n);++s){
          int top=0;
          memset(b,0x3f,sizeof(b));
        for(int j=1;j<=n;++j)if(!(s&(1<<j-1))){
            tag[top]=1<<j-1;
            for(int S=s;S;S-=(S&-S))if(e[j][ji[S&-S]+1]!=inf)b[top]=min(b[top],e[j][ji[S&-S]+1]*i);
            top++;
        }
        for(int j=1;j<(1<<top);++j){
            f[j]=f[j-(j&-j)]+b[ji[j&-j]];
            if(f[j]>=inf)f[j]=inf;
            be[j]=be[j-(j&-j)]|tag[ji[j&-j]];
            dp[i][s|be[j]]=min(dp[i][s|be[j]],dp[i-1][s]+f[j]);    
        }
      ans=min(ans,dp[i][(1<<n)-1]);
    }
    printf("%d",ans);
    return 0;
} 

T6列队

Solution

观察到最后一列很特殊,考虑分开维护。

对于每行的前m-1个点开线段树,里面记个size有表示这个位置没人。

那么整体向做移动怎么维护?

其实不用管它,毕竟对于前m-1列只会从后面插入。

那么对于一颗线段树,我们的操作只有从后面插入一个节点,从集合删去一个节点和查找第K个元素。

在最后一列开线段树,操作时分类讨论一下。

注意:n和m不要弄反,初始化size要为m-1不是m!!!

Code

#include<iostream>
#include<cstdio>
#define N 300002
using namespace std;
typedef long long ll;
int size[N],T[N],tot,q;
long long x,y,n,m;
struct node{ll first;bool second;};
struct seg{ll num;int l,r,sum;}tr[N*20];
node find(int &cnt,int l,int r,int k){
    if(!cnt)cnt=++tot;tr[cnt].sum++;
    if(l==r){
        if(tr[cnt].num)return node{tr[cnt].num,0};
        else return node{l,1};
    }
    int mid=(l+r)>>1,num=mid-l+1-tr[tr[cnt].l].sum;
    if(num>=k)return find(tr[cnt].l,l,mid,k);
    else return find(tr[cnt].r,mid+1,r,k-num);
} 
void add(int &cnt,int l,int r,int x,ll y){
    if(!cnt)cnt=++tot;
    if(l==r){tr[cnt].num=y;return;}
    int mid=(l+r)>>1;
    if(mid>=x)add(tr[cnt].l,l,mid,x,y);
    else add(tr[cnt].r,mid+1,r,x,y);
}
inline int rd(){
    int x=0;char c=getchar();while(!isdigit(c))c=getchar();
    while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();return x;
}
int main(){
    n=rd();m=rd();q=rd();
    for(int i=1;i<=n;++i)size[i]=m-1;//care
    size[n+1]=n;
    for(int i=1;i<=q;++i){
        x=rd();y=rd();
        if(y==m){
            node tmp=find(T[n+1],1,n+q,x);
            ll num=tmp.first;
            if(tmp.second)num=num*m;
            printf("%lld
",num);
            add(T[n+1],1,n+q,++size[n+1],num);
        }
        else{
            node tmp=find(T[x],1,m+q,y);
            ll num=tmp.first;
            if(tmp.second)num=num+(x-1)*m;
            printf("%lld
",num);
            tmp=find(T[n+1],1,n+q,x);
            ll num2=tmp.first;
            if(tmp.second)num2=num2*m;
            add(T[x],1,m+q,++size[x],num2);
            add(T[n+1],1,n+q,++size[n+1],num);
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/ZH-comld/p/9607257.html