首页 > 其他分享 >GYM100722C - Ticket to Ride

GYM100722C - Ticket to Ride

时间:2023-05-16 16:37:44浏览次数:45  
标签:GYM100722C rp int Ride cin rd msk Ticket dp

首先考虑 \(dp_{i,msk}\) 表示当前连通了 \(msk\) 中所有关键点,并且当前连通的非关键点包含 \(i\) 的最小代价。

然后考虑如何转移。我们先用 \(Floyd\) 预处理所有点对之间的最短路 \(dist_{i,j}\)。同时,每次选取的两个用于合并的关键点集合一定没有交集,所以我们可以直接枚举子集得到转移集合 \(S\) 和 \(msk/S\)。然后枚举转移用的非关键点 \(a\) 和 \(b\) 让 \(i\) 和它们都连通,\(dp_{a,msk}+dp_{b,msk/S}+dist_{i,a}+dist_{i,b}\) 就是答案。一个合法的最优解一定能通过断点的方式每次被划分成 \(dp_{i,msk}\) 的最优解,所以这样也一定能找到最优解。

最后还要合并答案,因为我们只是要给定的点对两两连通,并不需要所有的关键点连通。

我们可以枚举集合划分,被划分到同一个集合中的点对就要求全部连通,否则只要自己连通即可。\(4\) 的集合划分一共 \(15\) 种,我是提前打出划分数组然后枚举答案。 \(15\times2^8n\) 次。

枚举子集是 \(3^k\),复杂度 \(O(n^23^8)\),可以通过。

struct index{
	map<st,int>mp;
	int cnt=0;
	inline void ins(string s,int id){
		mp[s]=id;
	}
	inline int qry(string s){
		return mp[s];
	}
};
int n,m,c,f[35][35],dp[35][1<<8],ans[1<<8],to[4];
st x,y,s[8];
int d[15][4]={{0,0,0,0},{0,0,0,1},{0,0,1,0},{0,0,1,1},
	      {0,1,0,0},{0,1,0,1},{0,1,1,0},{0,1,1,1},
	      {0,0,1,2},{0,1,1,2},{0,1,2,2},{0,1,2,3},
	      {0,1,2,1},{0,1,2,0},{0,1,0,2}};
inline void solve(){
	rp(i,n)rp(j,n)f[i][j]=1e9;
	rp(i,n)f[i][i]=0;
	index idmap;
	rp(i,n){
		cin>>x;
		idmap.ins(x,i);
	}rp(i,m){
		cin>>x>>y>>c;
		int a=idmap.qry(x),b=idmap.qry(y);
		f[a][b]=min(f[a][b],c);
		f[b][a]=min(f[b][a],c);
	}rp(k,n)rp(i,n)rp(j,n)f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
	rd(i,4)cin>>s[i]>>s[4+i];
	rep(i,1,n)rd(j,(1<<8))dp[i][j]=1e9,ans[j]=1e9;
	rep(msk,1,(1<<8)-1)rp(i,n){
		int cnt=__builtin_popcount(msk);
		if(cnt==1){
			int d=__lg(msk&(-msk));
			dp[i][msk]=f[i][idmap.qry(s[d])];
			ans[msk]=0;
			continue;
		}
		for(int xx=msk-1;xx;xx=(xx-1)&msk){
			int yy=(msk^xx);
			rp(a,n)rp(b,n){
				dp[i][msk]=min(dp[i][msk],dp[a][xx]+dp[b][yy]+f[i][a]+f[i][b]);
			}
		}
		ans[msk]=min(ans[msk],dp[i][msk]);
	}
	int tans=1e9;
	rd(k,15){
		int cur=0;
		rd(c,4){
			int res=0,cans=1e9;
			rd(i,4)if(d[k][i]==c)res|=(1<<i),res|=(1<<(4+i));
			rep(msk,0,(1<<8)-1)if((msk&res)==res)cans=min(cans,ans[msk]);
			cur+=cans;
		}tans=min(tans,cur);
	}cout<<tans<<endl;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	while(n){
		solve();
		cin>>n>>m;
	}
	return 0;
}
//Crayan_r

但是这是比较暴力的做法,我们考虑更优一点的做法。

我们还是考虑 \(dp_{i,msk}\),每个 \(dp_{i,msk}\) 可以往 \(dp_{i,msk\cup S}\) 贡献 \(dp_{i,msk}\),可以往相邻的边 \(e\) 贡献 \(dp_{b_e,msk}\) 贡献 \(dp_{i,msk}+e.c\)。

这样每次的转移其实就是 \(2^8+n\) 的,会更优一些。

但是这个似乎阶段性不明显,我们发现转移的形式是总和最小值,考虑用 \(Dijkstra\) 进行转移。这样,我们每次提取出当前的 \(dp_{i,msk}\),枚举转移。点数是 \(O(n2^8)\),复杂度是 \(O(4^8n\log n)\)。但是仔细分析一下就会发现,有效(可能会更新栈)的转移只有 \(O(3^8n)\) 个,所以实际上的复杂度是 \(O(3^8n\log n+4^8n)\)。

然后考虑合并答案,我们可以记录 \(ans_i\) 表示 \(i\) 集合内的点对都已经满足要求的最小答案。用所有的 \(dp_{j,msk}\) 更新对应的 \(i\),然后枚举 \(a,b\),存在转移 \(ans_{a\cup b}=ans_a+ans_b\)。

最后输出 \(ans_{15}\) 即可。

实际跑出来,这个做法快了将近 \(30\) 倍。

int n,m,c,dp[35][256],ans[256];
st x,y,s[8];
vt<pii>vv[35];
map<string,int>id;
inline void solve(){
	id.clear();
	rp(i,n)vv[i].clear();
	rp(i,n){
		cin>>x;
		id[x]=i;
	}rp(i,m){
		cin>>x>>y>>c;
		int a=id[x],b=id[y];
		vv[a].pb({b,c});
		vv[b].pb({a,c});
	}
	rd(i,4)cin>>s[i]>>s[4+i];
	rp(i,n)rd(j,256)dp[i][j]=1e9,ans[j]=1e9;
	priority_queue<pair<int,pii> >pq;
	rd(i,8){
		int x=id[s[i]];
		dp[x][1<<i]=0;
		pq.push({0,{x,1<<i}});
	}while(pq.size()){
		int x=pq.top().second.first;
		int msk=pq.top().second.second;
		int val=-pq.top().first;pq.pop();
		if(val>dp[x][msk])continue;
		rd(y,256){
			if(dp[x][msk|y]>dp[x][msk]+dp[x][y]){
				dp[x][msk|y]=dp[x][msk]+dp[x][y];
				pq.push({-dp[x][msk|y],{x,msk|y}});
			}
		}for(auto e:vv[x]){
			int y=e.first,c=e.second;
			if(dp[y][msk]>dp[x][msk]+c){
				dp[y][msk]=dp[x][msk]+c;
				pq.push({-dp[y][msk],{y,msk}});
			}
		}
	}rp(i,n)rd(msk,256){
		int t=0;
		rd(j,4)if((msk>>j&1)&&(msk>>(j+4)&1)){
			t|=(1<<j);
		}ans[t]=min(ans[t],dp[i][msk]);
	}rd(i,16)rd(j,16)ans[i|j]=min(ans[i|j],ans[i]+ans[j]);
	cout<<ans[15]<<endl;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	while(n){
		solve();
		cin>>n>>m;
	}
	return 0;
}
//Crayan_r

标签:GYM100722C,rp,int,Ride,cin,rd,msk,Ticket,dp
From: https://www.cnblogs.com/jucason-xu/p/17406036.html

相关文章

  • C++-[override]关键字使用详解
    本文介绍了C++override关键字使用详解以及与重载的区别。C++override关键字使用详解一、override作用二、override在基类与派生类的应用2.1.纯虚函数2.2.普通虚函数2.3.Override重写三、Override实例四、C++中重载(overload)与覆盖(override)4.1.重载(overload)4.2.重写/覆......
  • C++ 11 :override 关键字的使用
    override关键字作用:在成员函数声明或定义中,override确保该函数为虚函数并覆写来自基类的虚函数。位置:函数调用运算符之后,函数体或纯虚函数标识“=0”之前。使用以后有以下好处:1.可以当注释用,方便阅读2.告诉阅读你代码的人,这是方法的复写3.编译器可以给你验证override......
  • Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?
    方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方......
  • 如何解决Gridea部分主题不渲染Katex的问题
    很多好看的主题因为对象不是信息学,所以忽视了公式,即\(\LaTeX\)。导致,如果你想渲染一个\(n\),结果成了nn这个简单,导入文件即可。找到主题文件夹,打开templates->post.ejs。添加以下这行代码:<linkrel="stylesheet"`href="https://cdn.jsdelivr.net/npm/[email protected]/......
  • 云原生之使用Docker部署PESMCS Ticket工单系统
    (云原生之使用Docker部署PESMCSTicket工单系统)一、PESMCSTicket介绍PESMCSTicket是一款基于GPLv2协议发布的开源客服工单系统。二、检查本地系统环境1.检查系统版本[root@node~]#cat/etc/os-releaseNAME="CentOSLinux"VERSION="7(Core)"ID="centos"ID_LIKE=......
  • DBGridEH11学习EhLib11的下载和安装(01)
    链接:https://pan.baidu.com/s/1HBw6AEzOXQgS2MIhh-ovhw提取码:iuuw安装  目录里Demos和Hlp文件夹, ......
  • Rider-调试并配置本地IIS
    项目部署到IISIIS:新建Web站点,路径指向Web应用程序根目录,端口默认80端口;应用程序池:".NetCLR版本"选择.NetCLR版本v4.0.30319,托管管道模式选择"集成"。  Web项目配置在Rider中选中Web项目,输入F4,打开csproj文件,添加如下配置。1<WebProjectProperties>2<U......
  • java笔记(this,super,override,instanceof,static)
    super关键字的一些注意事项子类在执行构造方法时,如果显式使用super()显式调用父类构造方法,则该调用必须放代码块在第一行super必须出现在子类的方法或者构造方法中使用this()显示调用构造方法,则该调用必须放在代码块第一行由于第一条和第三条限制,super和this不能同时调用构造......
  • dbgrideh也能类似于Excel下拉筛选的功能
    需要五个控件:DBGridEh,TDataSource,TClientDataSet,TMemTableEh,TDataSetDriverEh DBGridEh.DataSource >TDataSourceTDataSource.DataSet>TMemTableEhTMemTa......
  • C# override详解
    https://blog.csdn.net/u011555996/article/details/106751111重载、重写、覆写,分别指的是overload、override、new。一、override重写,是在子类中重写父类中的方法,两个函......