• NOIP2017提高组题解


    D1T1小凯的疑惑((OK))

    D1T2时间复杂度

    D1T3逛公园((OK))

    D2T1奶酪((OK))

    D2T2宝藏((OK))

    D2T3列队

    话说我真的不是故意每一年都只做(4)道的,而是每年剩下两道都不可做...其实时间复杂度不难,但因为是字符串(+)大模拟就咕咕咕了.

    (D1T1)今天重新写的时候早忘记结论了,就想说自己推,然后利用类似于完全背包的东西随便打了个表,十几分钟就发现了规律.

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<map>
    #include<set>
    #define ll long long
    using namespace std;
    inline int read(){
        int x=0,o=1;char ch=getchar();
        while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')o=-1,ch=getchar();
        while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
        return x*o;
    }
    //int f[10005];
    int main(){
    	/*for(int a=2;a<=20;++a){
    		for(int b=2;b<=20;++b){
    			cout<<a<<" "<<b<<":";
    			memset(f,0,sizeof(f));f[0]=1;
    			for(int i=a;i<=10000;++i)f[i]|=f[i-a];
    			for(int i=b;i<=10000;++i)f[i]|=f[i-b];
    			for(int i=10000;i>=2;--i)if(!f[i]){cout<<i<<endl;break;}
    		}
    	}*/
    	ll a=read(),b=read();
    	printf("%lld
    ",1ll*(b-2)*a+(a-b));
        return 0;
    }
    

    (D1T2)字符串+大模拟=咕咕咕.

    (D1T3)做了半个上午,每次碰到这种图论题一下正图一下反图的,脑袋就不好使了(可能是一直都不太好使吧).

    大概讲一下,就是先建个反图,从终点(n)开始跑最短路,记(dis[i])表示节点(i)到终点(n)的最短路的长度.然后建正图,设(f[i][j])表示从节点(i)到终点(n),当前路径长度还能够比最短路长(j)(即你还可以比最短路多走j的长度)的路径方案数.所以最终答案就是(f[1][k]).

    然后考虑怎么求这个东西,(DP)或者记忆化搜索.记忆化搜索虽然本质上就是(DP),但是代码实现起来比(DP)简单多了.考虑当前在节点(u),有一条边((u,v,w)),那么(f[u][k]+=f[v][k-(dis[v]+w-dis[u])]),很好理解,因为在节点(u)时还剩下多余的(k)步可走,那么从(u->v)我们浪费了(dis[v]+w-dis[u]),所以在节点(v)时就只剩下(k-(dis[v]+w-dis[u]))多余的步了.

    然后还要考虑判断无穷多解的情况,无穷多解即有一条合法路径上出现了(0)环,那么在记搜的同时再开一个数组(g)记录当前这个状态(g[i][j])有没有访问过,如果这个状态之前已经访问过了,说明出现了(0)

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<map>
    #include<set>
    #define ll long long
    using namespace std;
    inline int read(){
        int x=0,o=1;char ch=getchar();
        while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')o=-1,ch=getchar();
        while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
        return x*o;
    }
    const int N=100005;
    const int M=200005;
    int n,m,K,p,a[M],b[M],c[M];
    int tot,head[N],nxt[M],to[M],w[M];
    int dis[N],visit[N],f[N][51],g[N][51];
    inline void add(int a,int b,int c){
    	nxt[++tot]=head[a];head[a]=tot;
    	to[tot]=b;w[tot]=c;
    }
    inline void dij(){
    	memset(visit,0,sizeof(visit));
    	memset(dis,0x3f,sizeof(dis));
    	priority_queue<pair<int,int> >q;
    	q.push(make_pair(0,n));dis[n]=0;
    	while(q.size()){
    		int u=q.top().second;q.pop();
    		if(visit[u])continue;visit[u]=1;
    		for(int i=head[u];i;i=nxt[i]){
    			int v=to[i];
    			if(dis[v]>dis[u]+w[i]){
    				dis[v]=dis[u]+w[i];
    				q.push(make_pair(-dis[v],v));
    			}
    		}
    	}
    }
    inline int dfs(int u,int k){//记忆化搜索
    	if(g[u][k])return -1;//这个状态之前访问过=出现了0环
    	if(f[u][k])return f[u][k];//之前搜过这个状态,就不再搜了
    	g[u][k]=1;//标记该状态访问过
        f[u][k]=(u==n);//初始化
    	for(int i=head[u];i;i=nxt[i]){
    		int v=to[i],dist=dis[v]+w[i]-dis[u];
    		if(k-dist>=0){
    			int cnt=dfs(v,k-dist);
    			if(cnt==-1)return f[u][k]=-1;
    			else f[u][k]=(f[u][k]+cnt)%p;
    		}
    	}
    	g[u][k]=0;//回溯
        return f[u][k];
    }
    int main(){
    	int T=read();
    	while(T--){
    		n=read();m=read();K=read();p=read();
    		tot=0;memset(head,0,sizeof(head));
    		for(int i=1;i<=m;++i){
    			a[i]=read();b[i]=read();c[i]=read();
    			add(b[i],a[i],c[i]);
    		}
    		dij();//反向图最短路
    		tot=0;memset(head,0,sizeof(head));//初始化建正图
    		for(int i=1;i<=m;++i)add(a[i],b[i],c[i]);
    		memset(f,0,sizeof(f));memset(g,0,sizeof(g));
    		printf("%d
    ",dfs(1,K));
    	}
        return 0;
    }
    
    

    (D2T1)方法好像挺多的,我觉得我能够一下想到的,应该也是最容易想到的方法吧.就是(BFS),初始时把每个能从(z=0)钻入的洞丢进队列,每次暴力枚举扩展相连通的洞,找到一个能够到达(z=h)的洞即可.

    然后我自己(WA)了几次是因为几何数学没有学好???对于一个洞的球心((x,y,z)),如果它要能够到达(z=0/h),那么只要(dist((x,y,z),(0,0,0/h))<=r)即可,而我们(BFS)扩展的时候,对于两个洞的球心((x1,y1,z1),(x2,y2,z2)),这两个洞要连通只要保证(dist<=2*r).(之前没考虑好,全都是写的小于等于(2r),竟然还有(70)分)

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<map>
    #include<set>
    #define ll long long
    using namespace std;
    inline int read(){
        int x=0,o=1;char ch=getchar();
        while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')o=-1,ch=getchar();
        while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
        return x*o;
    }
    const int N=1005;
    int n,h,r,visit[N];
    struct dong{int x,y,z;}a[N];
    inline double dis(int i,int j){
    	return (double)sqrt(1.0*(a[i].x-a[j].x)*(a[i].x-a[j].x)+1.0*(a[i].y-a[j].y)*(a[i].y-a[j].y)+1.0*(a[i].z-a[j].z)*(a[i].z-a[j].z));
    }
    int main(){
    	int T=read();
    	while(T--){
    		memset(visit,0,sizeof(visit));queue<int>q;
    		n=read();h=read();r=read();a[0].x=0;a[0].y=0;a[0].z=0;
    		for(int i=1;i<=n;++i){
    			a[i].x=read();a[i].y=read();a[i].z=read();
    			if((double)sqrt(1.0*a[i].z*a[i].z)<=(double)r)q.push(i),visit[i]=1;
    		}
    		a[n+1].x=0;a[n+1].y=0;a[n+1].z=h;int bj=0;
    		while(q.size()){
    			int u=q.front();q.pop();
    			if((double)sqrt(1.0*(a[u].z-h)*(a[u].z-h))<=(double)r){bj=1;break;}
    			for(int i=1;i<=n;++i){
    				if(visit[i])continue;
    				if(dis(u,i)<=(double)2.0*r){q.push(i);visit[i]=1;}
    			}
    		}
    		if(bj)puts("Yes");
    		else puts("No");
    	}
        return 0;
    }
    
    

    (D2T2) (n<=12)很明显的状压,然后刚开始状态设错,搞了半个上午都没做出来.只好去看题解了

    因为(DP)扩展状态的时候 会发现贡献与当前这个节点距离根节点(最开始选的那一个宝藏屋)有关,所以考虑把这个设入状态.

    (f[i][j])表示当前考虑好了的点集是(j),扩展到了第(i)层(即点集(j)中距离根节点最深的节点的深度是(i))是的最小花费.

    (f[i][j|k]=min(f[i][j|k],f[i-1][j]+dist[j][k]*(i-1))).(j,k)是两个互不相交的集合,(dist[j][k])表示使得(j,k)这两个集合连通的最短距离.

    我们可以先预处理(dis[i][j])表示点(i)到点集(j)的最短距离(点(i)与点集(j)中的点直接连通).然后再通过(dis[i][j])求出(dist[i][j]).注意到我们需要枚举与一个集合互不相交的所有集合,其实就是该集合的补集的所有子集.

    我为了卡常,把所有的(memset)改成了循环,所以代码可能很丑陋.为什么最近做的几道(NOIP)题都需要卡常啊,本题卡常前总用时(5.45s),(T)(4)个点,卡常后总用时(3.53s),(AC).区别还是挺大的啊.

    本题因为(NOIP)的数据水,还有各种奇怪又美妙的做法,时间复杂度能够吊打状压.

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<map>
    #include<set>
    #define rg register
    #define ll long long
    using namespace std;
    inline int read(){
        rg int x=0,o=1;char ch=getchar();
        while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')o=-1,ch=getchar();
        while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
        return x*o;
    }
    const int inf=2e9;
    int w[15][15],dis[15][1<<12],dist[1<<12][1<<12],f[15][1<<12];
    int main(){
    	rg int n=read(),m=read();
    	for(rg int i=1;i<=n;++i)
    		for(rg int j=1;j<=n;++j)w[i][j]=inf;
    	for(rg int i=1;i<=m;++i){
    		rg int a=read(),b=read(),c=read();
    		if(c<w[a][b])w[a][b]=c,w[b][a]=c;//显然有重边
    	}
    	rg int S=(1<<n)-1;
    	for(rg int i=1;i<=n;++i)
    		for(rg int j=1;j<=S;++j)dis[i][j]=inf;
    	for(rg int i=1;i<=n;++i){//预处理dis数组
    		for(rg int j=1;j<=S;++j){
    			if(!(j&(1<<(i-1)))){
    				for(rg int k=1;k<=n;++k)
    					if(j&(1<<(k-1)))dis[i][j]=min(dis[i][j],w[k][i]);
    			}
    		}
    	}
    	for(rg int i=1;i<=S;++i){//预处理dist[i][j]
    		for(rg int j=i^S;j;j=(j-1)&(i^S)){//枚举子集
    			for(rg int k=1;k<=n;++k){
    				if((j&(1<<(k-1)))){
    					if(dis[k][i]==inf){dist[i][j]=0;break;}
    //只要集合j中有一个点无法到达集合$i$,就说明这两个集合不能连通
    //因为这里的连通都是建立在直接相连的意义下的,这样才能保证后面一层一层更新的正确性
    					dist[i][j]+=dis[k][i];
    				}
    			}
    		}
    	}
    	rg int ans=2e9;
    	for(rg int st=1;st<=n;++st){//枚举根节点
    		for(rg int i=1;i<=n;++i)
    			for(rg int j=0;j<=S;++j)f[i][j]=inf;
    		f[1][1<<(st-1)]=0;//初始化
    		for(rg int i=2;i<=n;++i){
    			for(rg int j=1;j<=S;++j){
    				for(rg int k=j^S;k;k=(k-1)&(j^S)){
    					if(dist[j][k])f[i][j|k]=min(f[i][j|k],f[i-1][j]+dist[j][k]*(i-1));
    				}
    			}
    		}
    		for(rg int i=1;i<=n;++i)if(f[i][S]<ans)ans=f[i][S];
    	}
    	printf("%d
    ",ans);
        return 0;
    }
    
    
  • 相关阅读:
    [NOI2019] 回家路线
    [NOIP2016] 天天爱跑步
    [CF1187D] Subarray Sorting
    [THUPC2018] 弗雷兹的玩具商店
    [AGC006C] Rabbit Exercise
    [AGC005F] Many Easy Problems
    [51Nod2558] 选址
    [BZOJ3771] Triple
    [APIO2019] 奇怪装置
    [CTSC2018] 假面
  • 原文地址:https://www.cnblogs.com/PPXppx/p/11765564.html
Copyright © 2020-2023  润新知