zoukankan      html  css  js  c++  java
  • NOIP2017 题解

    QAQ……由于没报上名并没能亲自去,自己切一切题聊以慰藉吧……
    可能等到省选的时候我就没有能力再不看题解自己切省选题了……辣鸡HZ毁我青春

    D1T1 小凯的疑惑

    地球人都会做,懒得写题解了……

    D1T2 时间复杂度

    分类讨论+递归就行了,没啥思维含量,略。

    D1T3 逛公园

    这题好劲啊……
    看见(kle 50)应该能想到这是一个(O((n+m)k))的DP,由于题目要求的是比最短路长度长至多(k)的路径条数,因此状态定义应该是定义(f_{i,j})表示从(i)走到终点,长度为(i)到终点的最短路长度+(j)的路径条数(如果你习惯倒序定义的话)。
    转移方程也不难写出:

    [f_{i,j}=sum_{<i,k>,weight=w}f_{k,d_i+j-w-d_k} ]

    其中(d_i)表示(i)到终点的最短路长度,边界是(f_{n,0}=1)
    注意到这个转移方程存在同层转移的问题:
    定义最短路DAG是所有点和所有满足(d_i=d_j+w)的边(<i,j>,w)组成的图(当然由于零边的存在,这个图上可能会有零环),那么转移时所有最短路DAG上的边都会有同层转移的问题。
    如果有一个可以到达(总路程不超过(D+k))的零环那显然答案就应该是-1了。因此我们可以对最短路DAG进行拓扑排序,最后能出来的点(也就是不能沿最短路到达零环的点)一定组成一个DAG。
    所以只需要判断那些出不来(在零环上或者通向零环)的点是否可达就行了。如果答案不是-1的话就只需要转移那些能出来的点,为了解决同层转移的问题,对每层先从低层转移过来,再按照最短路DAG的拓扑序同层转移即可。这样做不需要递归,常数比记忆化搜索小很多。
    (注意由于数据范围比较大,最开始求(d_i)和构造最短路DAG时要用Dijkstra算法,不要用SPFA。)
    莫名其妙的地方都能写错,老年选手身败名裂……

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #include<queue>
    using namespace std;
    const int maxn=100005,maxm=200005;
    struct A{
    	int x,d;
    	A(int x,int d):x(x),d(d){}
    	bool operator<(const A &a)const{return d>a.d;}
    };
    void Dijkstra(int);
    void bfs();
    vector<int>G[maxn],W[maxn],G2[maxn],RG2[maxn];
    bool vis[maxn];
    int d[maxn],dis[maxn],du[maxn],q[maxn],u[maxm],v[maxm],w[maxm];
    int T,n,m,k,p,f[maxn][55],head,tail;
    int main(){
    	scanf("%d",&T);
    	while(T--){
    		memset(du,0,sizeof(du));
    		memset(f,0,sizeof(f));
    		scanf("%d%d%d%d",&n,&m,&k,&p);
    		for(int i=1;i<=n;i++){
    			G[i].clear();
    			W[i].clear();
    			G2[i].clear();
    			RG2[i].clear();
    		}
    		for(int i=1;i<=m;i++){
    			scanf("%d%d%d",&u[i],&v[i],&w[i]);
    			G[v[i]].push_back(u[i]);
    			W[v[i]].push_back(w[i]);
    		}
    		Dijkstra(n);
    		memcpy(d,dis,sizeof(d));
    		for(int i=1;i<=n;i++){
    			G[i].clear();
    			W[i].clear();
    		}
    		for(int i=1;i<=m;i++){
    			G[u[i]].push_back(v[i]);
    			W[u[i]].push_back(w[i]);
    		}
    		Dijkstra(1);
    		for(int i=1;i<=m;i++)if(d[u[i]]==d[v[i]]+w[i]){
    			//printf("(%d,%d) w=%d
    ",u[i],v[i],w[i]);
    			G2[u[i]].push_back(v[i]);
    			RG2[v[i]].push_back(u[i]);
    			du[u[i]]++;
    		}
    		bfs();
    		bool bad=false;
    		for(int i=1;i<=n;i++)if(!vis[i]&&d[i]+dis[i]<=d[1]+k){
    			bad=true;
    			break;
    		}
    		if(bad){
    			printf("-1
    ");
    			continue;
    		}
    		//for(int i=1;i<=n;i++)if(!vis[i])printf("d[%d]=%d dis[%d]=%d D=%d k=%d
    ",i,d[i],i,dis[i],d[1],k);
    		f[n][0]=1;
    		for(int j=0;j<=k;j++){
    			if(j){
    				for(int x=1;x<=n;x++)if(vis[x])for(int i=0;i<(int)G[x].size();i++){
    					int t=d[x]-d[G[x][i]]+j-W[x][i];//printf("%d %d t=%d
    ",x,G[x][i],t);
    					if(t>=0&&t<j){
    						f[x][j]=f[x][j]+f[G[x][i]][t];
    						if(f[x][j]>=p)f[x][j]-=p;
    					}
    				}
    			}
    			for(int i=0;i<tail;i++){
    				int x=q[i];
    				for(int t=0;t<(int)G2[x].size();t++){
    					f[x][j]=f[x][j]+f[G2[x][t]][j];
    					if(f[x][j]>=p)f[x][j]-=p;
    				}
    			}
    			//for(int i=1;i<=n;i++)printf("f[%d][%d]=%d
    ",i,j,f[i][j]);
    		}
    		int ans=0;
    		for(int i=0;i<=k;i++){
    			ans+=f[1][i];
    			if(ans>=p)ans-=p;
    		}
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    void Dijkstra(int x){
    	memset(dis,63,sizeof(dis));
    	memset(vis,0,sizeof(vis));
    	priority_queue<A>heap;
    	dis[x]=0;
    	heap.push(A(x,0));
    	while(!heap.empty()){
    		x=heap.top().x;
    		heap.pop();
    		vis[x]=true;
    		for(int i=0;i<(int)G[x].size();i++)if(!vis[G[x][i]]&&dis[G[x][i]]>dis[x]+W[x][i]){
    			dis[G[x][i]]=dis[x]+W[x][i];
    			heap.push(A(G[x][i],dis[G[x][i]]));
    		}
    	}
    }
    void bfs(){
    	head=tail=0;
    	for(int i=1;i<=n;i++)if(!du[i])q[tail++]=i;
    	memset(vis,0,sizeof(vis));
    	while(head!=tail){
    		int x=q[head++];//printf("%d ",x);
    		vis[x]=true;
    		for(int i=0;i<(int)RG2[x].size();i++)if(!--du[RG2[x][i]])q[tail++]=RG2[x][i];
    	}
    	//printf("
    ");
    }
    

    D2T1 奶酪

    (O(n^2))暴力就行了,水题。

    D2T2 宝藏

    看到数据范围一眼(O^*(3^n))状压DP,其中(3^n)来自枚举子集的子集。做法好像有很多,比如ryf的做法就比我快了10倍……日渐辣鸡的窝
    定义(f_{i,j,S})表示以(i)为根的子树,(i)在整棵树里的深度是(j),集合(S)中的所有点都在这棵子树中时这棵子树的最小总代价。
    不难写出转移方程:

    [f_{i,j,S}=min_{Tsubsetneq S,kin T}{f_{k,j+1,T}+f_{i,j,S-T}+j imes w_{i,k}} ]

    (这个转移方程是在枚举与(i)相邻的一个点(k)并把以(k)为根的子树分出去)
    然而你发现这个DP是(O(n^3 3^n))的,并不能跑过去。
    考虑优化转移,如果把转移方程中与(S)无关的部分拿出来并定义一个辅助数组

    [g_{i,j,T}=min_{kin T}{f_{k,j+1,T}+j imes w_{i,k}} ]

    的话,我们就可以把状态转移方程改写成

    [f_{i,j,S}=min_{Tsubsetneq S}{f_{i,j,S-T}+g_{i,j,T}} ]

    这样就可以把复杂度降到(O(n^2 3^n))了,由于常数不大,并不需要卡常。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define bit(x) (1<<((x)-1))
    using namespace std;
    const int INF=0x3f3f3f3f;
    int n,m,w[15][15],f[15][15][(1<<12)+1],g[15][15][(1<<12)+1];
    int main(){
    	scanf("%d%d",&n,&m);
    	memset(w,63,sizeof(w));
    	memset(f,63,sizeof(f));
    	memset(g,63,sizeof(g));
    	while(m--){
    		int x,y,z;
    		scanf("%d%d%d",&x,&y,&z);
    		w[x][y]=w[y][x]=min(w[x][y],z);
    	}
    	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)f[i][j][bit(i)]=0;
    	for(int j=n-1;j;j--)for(int s=0;s<(1<<n);s++)for(int i=1;i<=n;i++){
    		for(int k=1;k<=n;k++)if((bit(k)|s)==s&&w[i][k]<INF)
    			g[i][j][s]=min(g[i][j][s],f[k][j+1][s]+w[i][k]*j);
    		for(int t=s&(s-1);;(--t)&=s){
    			f[i][j][s]=min(f[i][j][s],f[i][j][s^t]+g[i][j][t]);
    			if(!t)break;
    		}
    	}
    	int ans=INF;
    	for(int i=1;i<=n;i++)ans=min(ans,f[i][1][(1<<n)-1]);
    	printf("%d",ans);
    	return 0;
    }
    

    D2T3 列队

    这题真是劲啊……比往年的数据结构NB到不知哪儿去了……
    (码力太差还没写出来,写出来之后再补题解)

  • 相关阅读:
    杜教筛学习笔记
    Dirichlet 卷积学习笔记
    洛谷 [POI2007]BIU-Offices 解题报告
    NOIP 2018 游记
    洛谷 P4964 绫小路的特别考试 解题报告
    洛谷 P4597 序列sequence 解题报告
    洛谷 P2757 [国家集训队]等差子序列 解题报告
    对答案 解题报告
    multimap-find
    multimap-insert
  • 原文地址:https://www.cnblogs.com/hzoier/p/7857587.html
Copyright © 2011-2022 走看看