题解

cookiebus 2023-08-07 10:37:30 2023-08-08 9:57:28 4 返回题目

原题出处:CF1209F

数据太难造了,卡掉了比较显然的暴力,但是很多假做法就卡不掉了,所以赛时放了一些假做法高分。

10分:暴力dfs找到1号点到其他城市的所有路径,一旦更新了答案再继续 dfs下去即可。

30分:直接无脑用string来做dijkstra,复杂度刚刚够用。

100分:

做法1: 考虑先求出从起点到每一个点的最小数字串长度,注意,此处我们只是关心串的长度。

做完这个事情以后,哪些边是有可能被用到的,就已经确定好了。

我们此时对所有可能被用到的边进行拆边,例如一条边编号是364,我们就拆成3,6,4的三条边。

接下来,从起点出发进行dfs,每次贪心的走当前编号最小的边,如果对应的点还没有被走过,就走过去。

#include<bits/stdc++.h>
#define mod 1000000007
#define maxn 100005
#define ll long long
#define mk(a,b) make_pair(a,b)
using namespace std;
int n,m,tot,len[maxn];
basic_string<pair<int,int>> edge[maxn],E[maxn*6];
int cal(int x) {int ret=0; while (x) {ret++; x=x/10;} return ret;}
struct nod{
	int id,d;
	nod(){}
	nod(int a,int b) {id=a; d=b;}
	bool operator<(const nod&x)const{return d>x.d;}
};

void addedge(int u,int v,int ID)
{
	int c[8],id[8],cnt=0;
	while (ID) {c[++cnt]=ID%10; ID/=10;}
	for (int i=cnt;i>=2;i--) id[i]=++tot;
	id[1]=v; id[cnt+1]=u;
	for (int i=cnt+1;i>=2;i--) E[id[i]]+=mk(id[i-1],c[i-1]);
	return;
}

int cmp(pair<int,int> x,pair<int,int> y) {return x.second<y.second;}

priority_queue<nod> Q;
int vis[maxn],dis[maxn];
void dijk()
{
	memset(dis,0x3f,sizeof(dis)); dis[1]=0;
	Q.push(nod(1,0));
	while (!Q.empty())
	{
		nod now=Q.top(); Q.pop();
		if (vis[now.id]) continue;
		vis[now.id]=1;
		for (auto nxt:edge[now.id])
		{
			if (dis[nxt.first]>now.d+len[nxt.second])
			{
				dis[nxt.first]=now.d+len[nxt.second];
				Q.push(nod(nxt.first,dis[nxt.first]));
			}
		}
	}
//	for (int i=1;i<=n;i++) cout<<dis[i]<<" ";
	
	for (int i=1;i<=n;i++)
		for (auto nxt:edge[i])
		if (dis[i]+len[nxt.second]==dis[nxt.first])
		{
			addedge(i,nxt.first,nxt.second);
		}
	for (int i=1;i<=tot;i++) sort(E[i].begin(),E[i].end(),cmp);
}

int ans[maxn*5];
void dfs(int i)
{
//	cout<<i<<" "<<ans[i]<<endl;
	for (auto nxt:E[i])
	if (!ans[nxt.first] && nxt.first!=1)
	{
		ans[nxt.first]=(ans[i]*10ll+nxt.second)%mod;
		dfs(nxt.first);
	}
}

int main()
{
	cin>>n>>m;
	for (int i=1;i<=m;i++) len[i]=cal(i);
	tot=n; int u,v;
	for (int i=1;i<=m;i++) {cin>>u>>v;  edge[u]+=mk(v,i); edge[v]+=mk(u,i);}
	dijk();
	dfs(1);
	for (int i=2;i<=n;i++) cout<<ans[i]<<endl;
}

但实际上上述做法是错的哦。(我专门写了这个做法,用来造数据的时候卡掉它)。

举一个反例:

节点1有好几个出边,出边都是3,一个是338拆出来的,一个是33拆出来的。

这时候你先搜哪一个3都不对,因为你并不知道那个33后面跟的是6还是9(PS:33后面跟的是DAI)。

所以这个做法是假的,但造数据的人还是很良心的造了45分。

做法2:

我们一开始就把边拆好,所有边长度都是 1 每次扩展的时候,把一次性能扩展出的长度一样,且字典序一样的边都扩展出来。

具体是:对于当前所有距离一模一样的点,存入同一个 vector,然后一起枚举他们的编号为 的出边,一次性把相同距离的点都扩展出来,翻译成代码长这样:

vector<int> G[N][10], q[N];
for(int i = 1; i <= T; ++ i) {
        for(int j = 0; j <= 9; ++ j) {//优先走数字小的
            bool flag = 0;
            for(auto it : q[i])//对于同一个i,q[i]中的点的ans都是一样的
                for(auto v : G[it][j]) {
                    if(vis[v]) continue;
                    vis[v] = flag = 1;
                    q[T+1].push_back(v);
                    ans[v] = (10ll*ans[it]+j) % mod;//更新答案
                }
            if(flag) T++; //如果有拓展出新的节点,则下一次拓展出的点要放到不同的vector中
        }
    }

完整的代码:

//格外注意一个细节就是,我们需要开9n*10个大约9e6个vector,这是300mb的静态空间,如果无脑开2e7个vector就会MLE,一分没有。
int cnt;
int ans[N];
bool vis[N];
vector<int> G[N][10], q[N];
int main() {
    int n, m;
    read(n,m);
    cnt = n;
    for(int i = 1; i <= m; ++ i) {
        int u, v;
        read(u,v);
        int tmp = i, pre = v;
        while (tmp>9) {
            G[++cnt][tmp%10].push_back(pre);
            tmp/=10;pre=cnt;
        }
        G[u][tmp].push_back(pre);
        tmp = i, pre = u;
        while(tmp>9) {
            G[++cnt][tmp%10].push_back(pre);
            tmp/=10;pre=cnt;
        }
        G[v][tmp].push_back(pre);
    }
    int T;
    q[++T].push_back(1);
    vis[1] = 1;
    for(int i = 1; i <= T; ++ i) {
        for(int j = 0; j <= 9; ++ j) {//优先走数字小的
            bool flag = 0;
            for(auto it : q[i])//对于同一个i,q[i]中的点的ans都是一样的
                for(auto v : G[it][j]) {//走过就不用再走了
                    if(vis[v]) continue;
                    vis[v] = flag = 1;
                    q[T+1].push_back(v);
                    ans[v] = (10ll*ans[it]+j) % mod;//更新答案
                }
            if(flag) T++;
        }
    }
    for(int i = 2; i <= n; ++ i) cout << ans[i] << endl;
}
{{ vote && vote.total.up }}