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\n"; continue; } if(ans==num)cout<<"Yes\n"; else cout<<"No\n"; } return 0; }
T3逛公園
T4奶酪
現有一塊大奶酪,它的高度為 hh,它的長度和寬度我們可以認為是無限大的,奶酪 中間有許多 半徑相同 的球形空洞。我們可以在這塊奶酪中建立空間坐標系,在坐標系中, 奶酪的下表面為z = 0z=0,奶酪的上表面為z = hz=h。
現在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐 標。如果兩個空洞相切或是相交,則 Jerry 可以從其中一個空洞跑到另一個空洞,特別 地,如果一個空洞與下表面相切或是相交,Jerry 則可以從奶酪下表面跑進空洞;如果 一個空洞與上表面相切或是相交,Jerry 則可以從空洞跑到奶酪上表面。
位於奶酪下表面的 Jerry 想知道,在 不破壞奶酪 的情況下,能否利用已有的空洞跑 到奶酪的上表面去?
#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\n"); else printf("No\n"); } return 0; }
T5寶藏
參與考古挖掘的小明得到了一份藏寶圖,藏寶圖上標出了 n 個深埋在地下的寶藏屋, 也給出了這 n 個寶藏屋之間可供開發的m 條道路和它們的長度。
小明決心親自前往挖掘所有寶藏屋中的寶藏。但是,每個寶藏屋距離地面都很遠, 也就是說,從地面打通一條到某個寶藏屋的道路是很困難的,而開發寶藏屋之間的道路 則相對容易很多。
小明的決心感動了考古挖掘的贊助商,贊助商決定免費贊助他打通一條從地面到某 個寶藏屋的通道,通往哪個寶藏屋則由小明來決定。
在此基礎上,小明還需要考慮如何開鑿寶藏屋之間的道路。已經開鑿出的道路可以 任意通行不消耗代價。每開鑿出一條新道路,小明就會與考古隊一起挖掘出由該條道路 所能到達的寶藏屋的寶藏。另外,小明不想開發無用道路,即兩個已經被挖掘過的寶藏 屋之間的道路無需再開發。
新開發一條道路的代價是:
L代表這條道路的長度,K代表從贊助商幫你打通的寶藏屋到這條道路起點的寶藏屋所經過的 寶藏屋的數量(包括贊助商幫你打通的寶藏屋和這條道路起點的寶藏屋) 。
請你編寫程序為小明選定由贊助商打通的寶藏屋和之後開鑿的道路,使得工程總代 價最小,並輸出這個最小值。
Solution
看到n非常小,考慮直接暴力枚舉每個起點,設dp[S]表示挖到的集合為S的最小花費,記憶化搜索即可,每次枚舉集合裏的一個點和一個新點(非常暴力對不對)。
Code
#include<iostream> #include<cstring> #include<cstdio> #define inf 0x7f7f7f7f using namespace std; int dis[20],l[20][20],n,m,a,b,c; long long dp[1<<12],ans; void dfs(int S) { for(int u=1;u<=n;++u) if(S&(1<<(u-1))) for(int v=1;v<=n;++v) if(!(S&(1<<(v-1)))&&l[u][v]!=inf&&dp[S|1<<(v-1)]>dp[S]+dis[u]*l[u][v]) { dp[S|(1<<(v-1))]=dp[S]+dis[u]*l[u][v]; dis[v]=dis[u]+1; dfs(S|(1<<(v-1))); dis[v]=inf; } } int main() { scanf("%d%d",&n,&m); memset(l,0x7f,sizeof(l)); for(int i=1;i<=m;++i) { scanf("%d%d%d",&a,&b,&c); l[a][b]=min(l[a][b],c); l[b][a]=l[a][b]; } ans=0x7f7f7f7f; for(int i=1;i<=n;++i) { memset(dis,0x7f,sizeof(dis)); memset(dp,0x7f,sizeof(dp)); dp[1<<(i-1)]=0; dis[i]=1; dfs(1<<(i-1)); ans=min(ans,dp[(1<<n)-1]); //cout<<dp[(1<<n)-1]<<" "; } cout<<ans; return 0; }
T6列隊
Solution
觀察到最後一列很特殊,考慮分開維護。
對於每行的前m-1個點開線段樹,裏面記個size有表示這個位置沒人。
那麽整體向做移動怎麽維護?
其實不用管它,畢竟對於前m-1列只會從後面插入。
在最後一列開線段樹,操作時分類討論一下。
Code
#include<iostream> #include<cstdio> #define ls tr[root].l #define rs tr[root].r #define N 300002 using namespace std; typedef long long ll; ll tot,q,T[N],t[N],m,n,x,y; ll rd(){ ll x=0;char c=getchar();while(!isdigit(c))c=getchar(); while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return x; } struct fd{ ll l,r,size,tag; }tr[6000002]; void ins(ll &root,ll l,ll r,ll x,ll y){ if(!root)root=++tot; if(l==r){ tr[root].tag=y; tr[root].size=0; return; } int mid=(l+r)>>1; if(mid>=x)ins(ls,l,mid,x,y); else ins(rs,mid+1,r,x,y); tr[root].size=tr[ls].size+tr[rs].size; } pair<ll,bool> find(ll &root,ll l,ll r,ll k,ll tag){ if(!root)root=++tot;tr[root].size++; if(l==r) if(r<=tag)return make_pair(l,1); else return make_pair(tr[root].tag,0); int mid=(l+r)>>1,cnt=(mid-l+1)-tr[ls].size; if(cnt>=k)return find(ls,l,mid,k,tag); else return find(rs,mid+1,r,k-cnt,tag); } int main(){ n=rd();m=rd();q=rd(); for(int i=1;i<=n;++i)t[i]=m-1; t[n+1]=n; for(int i=1;i<=q;++i){ x=rd();y=rd(); if(y==m){ pair<ll,bool> tmp=find(T[n+1],1,n+q,x,n);//在(n+1)棵線段樹中找x大 ll ji=tmp.first; if(tmp.second)ji*=m;//乘上列 printf("%lld\n",ji); ins(T[n+1],1,n+q,++t[n+1],ji);//在這一列中插入 } else{ pair<ll,bool>tmp=find(T[x],1,m+q,y,m-1);//在x行中找y大。 ll ji=tmp.first; if(tmp.second)ji+=(x-1)*m; printf("%lld\n",ji); pair<ll,bool>tmp2=find(T[n+1],1,n+q,x,n); ll ji2=tmp2.first; if(tmp2.second)ji2*=m; ins(T[x],1,m+q,++t[x],ji2); ins(T[n+1],1,n+q,++t[n+1],ji); } } return 0; }
NOIP2017題解