NOIP2017Day2题解

T1.

大力并查集一下就ok

考试的时候差点把n+1写成n,幸好最后时候检查出来了

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define N 2005
using namespace std;
long long x[N],y[N],z[N];
int a[N];
int n,T;
long long h,r;
long long heickr(long long x)
{
    return x*x;
}
int find(int x)
{
    if (x==a[x]) return x;
    a[x]=find(a[x]);
    return a[x];
}
void make(int x,int y)
{
    a[find(x)]=find(y);
}
int main()
{
    scanf("%d",&T);
    while (T--){
        scanf("%d%lld%lld",&n,&h,&r);
        for (int i=0;i<=n+1;i++) a[i]=i;
        for (int i=1;i<=n;i++) scanf("%lld%lld%lld",&x[i],&y[i],&z[i]);
        for (int i=1;i<=n;i++){
            if (z[i]-r<=0) make(0,i);
        }
        for (int i=1;i<=n;i++){
            if (z[i]+r>=h) make(i,n+1);
        }
        for (int i=1;i<n;i++){
            for (int j=i+1;j<=n;j++){
                if ((heickr(x[i]-x[j])+heickr(y[i]-y[j])+heickr(z[i]-z[j]))<=4*r*r) make(i,j);
            }
        }
        if (find(0)==find(n+1)) puts("Yes");
        else puts("No");
    }
    return 0;
}

T2.

其实是一道傻逼题,考场上最后时候想出来了,但是没有写粗来,来不及了

考虑暴力枚举从哪个点开始

然后进行dfs,用dp[i]表示已经打通的集合状态为i的最小代价

只有在集合中的元素才有dis值

然后暴力进行dfs,枚举j和k(j在集合中,k不在),表示打通j和k,然后计算一波贡献

讲k加入集合,给kdis值,注意回溯的时候还原k的dis即可

代码:

#include<bits/stdc++.h>
#define N 15
using namespace std;
int dp[1<<N],dis[N],f[N][N],n,m,x,y,z,ans=1e9;
inline bool in(int x,int S){return ((1<<(x-1))&S);}
void dfs(int S){
 	for (int i=1;i<=n;i++){
 		if (in(i,S)){
     		for (int j=1;j<=n;j++){
				if (!in(j,S)){
					if (f[i][j]>1e9) continue;
					int now=(1<<(j-1))+S;
					if (dp[now]>dp[S]+f[i][j]*dis[i]){
						dp[now]=dp[S]+dis[i]*f[i][j];
						int tmp=dis[j];
						dis[j]=dis[i]+1;
						dfs(now);
						dis[j]=tmp;
					}
				}
			}
		}
	}
}
inline void solve(int x){
	memset(dp,127,sizeof(dp));
	memset(dis,127,sizeof(dis));
	dp[1<<(x-1)]=0;dis[x]=1;
	dfs(1<<(x-1));
	ans=min(ans,dp[(1<<n)-1]);
}
int main(){
	memset(f,127,sizeof(f));
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		f[x][y]=f[y][x]=min(f[x][y],z);
	}
	for (int i=1;i<=n;i++) solve(i);
	printf("%d\n",ans);
	return 0;
}

T3.当时太单纯了,以为noip一定不会考数据结构,于是就想了半天都没有结果,然后敲了一个50分大暴力上去(最后还只拿了30分)<---弱

其实说真的,这题也不是很难,只要弄清楚它的操作过程的特点就行了

我们对于每一行开一棵动态开点线段树维护它除了最后一列的情况,然后0表示这个位置还存在,1表示没有了(这样就可以操作了,不会因为空间问题了)

对于最后一列我们专门开一个动态开点线段树来维护(之后动态开点线段树都简称为线段树)

然后对于每行以及最后一列开一个vector来存放后来放进去的元素

然后每次查询,假设查到的是线段树中第x位置,那么判断x<m还是>=m

如果<m说明是原来的,直接算

如果>=m说明后来进来的,直接在vector中查询即可,行列均同理的呀

注意特判查询的时候y==m的情况

代码:

#include<bits/stdc++.h>
#define N 9000005
#define M 300005
using namespace std;
int d[N],ls[N],rs[N],root[M],n,m,x,y,Q,tot,cnt;
vector<long long>G[M];
void insert(int &k,int l,int r,int x){
    if (!k) k=++cnt; d[k]++; if (l==r) return;
    int mid=(l+r)>>1; if (x<=mid) insert(ls[k],l,mid,x);
    else if (x>mid) insert(rs[k],mid+1,r,x);
}
int query(int k,int l,int r,int kth){
    if (l==r) return l; int mid=(l+r)>>1;
    int summ=mid-l+1-d[ls[k]]; if (summ>=kth) return query(ls[k],l,mid,kth);
    else if (summ<kth) return query(rs[k],mid+1,r,kth-summ);
}
inline long long calline(int x){//求最后一列第x个元素的值
    int r=query(root[n+1],1,tot,x);
    insert(root[n+1],1,tot,r);
    if (r<=n) return (long long)r*m;else return G[n+1][r-n-1];
}
inline long long calrow(int x,int y){
    int r=query(root[x],1,tot,y);
    insert(root[x],1,tot,r);
    if (r<m) return (long long)(x-1)*(long long)m+(long long)r;else return G[x][r-m];
}
int main(){
    scanf("%d%d%d",&n,&m,&Q); tot=Q+max(n,m);
    while (Q--){
        scanf("%d%d",&x,&y);
        if (y!=m){
            long long ret=calrow(x,y);
            G[n+1].push_back(ret); printf("%lld\n",ret);//把它放到最后一列 
            long long tmp=calline(x); G[x].push_back(tmp);//最后一列的第x个元素现在属于x行并且把不属于最后一列了,把它放到vector中 
        } else if (y==m){
            long long tmp=calline(x); printf("%lld\n",tmp);
            G[n+1].push_back(tmp);
        }
    }
    return 0;
}

  

猜你喜欢

转载自www.cnblogs.com/ckr1225/p/9033105.html