zoukankan      html  css  js  c++  java
  • 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;
    }
    
    
  • 相关阅读:
    教程:在 Visual Studio 中开始使用 Flask Web 框架
    教程:Visual Studio 中的 Django Web 框架入门
    vs2017下发现解决python运行出现‘No module named "XXX""的解决办法
    《sqlite权威指南》读书笔记 (一)
    SQL Server手工插入标识列
    hdu 3729 I'm Telling the Truth 二分图匹配
    HDU 3065 AC自动机 裸题
    hdu 3720 Arranging Your Team 枚举
    virtualbox 虚拟3台虚拟机搭建hadoop集群
    sqlserver 数据行统计,秒查语句
  • 原文地址:https://www.cnblogs.com/PPXppx/p/11765564.html
Copyright © 2011-2022 走看看