1 条题解

  • 0
    @ 2025-8-24 22:38:14

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Dr_Gilbert
    比赛审核事宜请在工单沟通,不要私信沟通。

    搬运于2025-08-24 22:38:14,当前版本为作者最后更新于2022-05-17 11:24:09,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    P8345 「Wdoi-6」华胥之梦

    UPD 2022-05-20: 修改一处笔误

    【题目大意】

    给定长度为 nn 的序列 aa 和常数 cc。构造点数为 nn 的有向完全图 GG 使得边 iji\to jiji\neq j)的长度为 ai2aj+ca_i-2a_j+c,保证所有边权非负

    接下来给出 qq 次询问,每次给出一个点集,试找出图 GG 的一条最短的简单路径,满足其经过点集中所有点,并输出它的长度。

    【数据范围】

    • N,Q,S106N,Q,\sum |S|\le10^6
    • 对于 20%20\% 的数据,有 aia_i 全部相同。
    • 对于另 20%20\% 的数据,有 aia_i 单调递增。

    看到这个题意和这个数据范围,感觉没有任何思路,不妨从特殊性质入手先拿部分分。aia_i 全部相同的部分分非常简单,也没有什么价值。比较关键的是 aia_i 全部递增这点性质,由于边权的定义为 ai2aj+ca_i-2a_j+c,不难想到一个贪心策略,那就是将点集中的点排序后直接走。如果这个策略正确,那么可以由这个策略推广,只需把点集中的点按 aia_i 的值升序排序后直接走即可。下面可以证明一下这个贪心策略。

    这个贪心策略的证明,在这里分两部分,第一部分是论证若所走的点一定是点集内的点,则路径长度最短;而第二部分是若走的顺序一定按 aia_i 大小,则路径最短。先论证第一部分。注意到题目中保证所有边权非负数,即

    ai2aj+c0a_i-2a_j+c\ge0

    对这个式子进行一些变换,就可以得到

    ai2aj+c0ajai+c2a_i-2a_j+c\ge0\Rightarrow a_j\le \frac{a_i+c}{2}

    对于这条边的反向边 jij \rightarrow i ,同理有

    aiaj+c2a_i\le\frac{a_j+c}{2}

    将上式中 aja_j 的最大值代入下式,有

    $$a_i\le \frac{\frac{a_i+c}{2}+c}{2} \Rightarrow 4a_i\le a_i+3c \Rightarrow a_i\le c $$

    显然,aia_iaja_j 的增大而增大,所以 aia_i 的最大值为 cc。假设在从点 iijj 时,经过了点 kk,即路径为 ikji\rightarrow k \rightarrow j,则总长度为

    $$a_i-2a_k+c+a_k-2a_j+c=a_i-a_k-2a_j+2c\\ \because a_k\le c\\ \therefore c-a_k\ge 0\\ \therefore a_i-a_k-2a_j+2c\ge a_i-2a_j+c $$

    所以如果不需要经过点 kk,那么就不要走点 kk,即若所走的点一定是点集内的点,则路径长度最短,第一部分得证。接下来论证第二部分:

    由第一部分的结论,最短路径上的点一定都在点集内。且由边权非负,可得所走路径一定是按某个顺序依次连接点集中所有点的一条链。所以对于点集 SS,最短路径的边数是确定的,即 S1|S|-1。所以总代价中的常数项是确定的。接下来可以对某个点集中某条路径的总长度的式子进行简单推导,设点集中各点按一定顺序排列后分别为 S(1)S(n)S(1)\ldots S(n),则这条路径的总路径长为

    $$a_{S(1)}-2a_{S(2)}+a_{S(2)}-2a_{S(3)}+\cdots+a_{S(n-1)}-2a_{S(n)}+(n-1)c $$

    化简得

    $$(n-1)c+a_{S(1)}-2a_{S(n)}-\sum_{i=2}^{n-1} a_{S(i)} $$

    可见,最终的路径长度只和路径的起点和终点有关。只需保证起点的 aia_i 最小且重点的 aia_i 最大,即可保证总长度最小,而排序就可以保证这一条件,第二部分得证。

    当然,从第二部分的证明可以发现,完全没有必要对集合 SS 进行排序,只需找出最大值和最小值即可。这样,时间复杂度就降到了 O(S)O(\sum|S|)

    当然,这道题时限 1.5 s1.5 \text{ s},并没有卡每次排序的做法。赛时时间紧,没多想就写了每次排序的做法,时间复杂度 O(nlogn+SlogS)O(n\log n+\sum |S|\log |S|)

    代码如下:

    #include <bits/stdc++.h>
    #define int long long
    using namespace std;
    struct po{
    	int val,x;
    	bool operator <(const po& a)const{
    		return val<a.val;
    	}
    }p[1000010];
    int s[1000010],a[1000010];
    bool cmp(int x, int y){return (a[x]<a[y]);}
    bool cmp2(po a, po b){return a.x<b.x;}
    signed main(){
    	int n,c,q;
    	cin>>n>>c>>q;
    	for (int i=1;i<=n;i++){
    		cin>>p[i].val;
    		p[i].x=i;
    	}
    	sort(p+1,p+1+n);//排序
    	for (int i=1;i<=n;i++) a[p[i].x]=i; // 存排名
    	sort(p+1,p+1+n,cmp2);
    	while (q--){
    		int k;cin>>k;
    		for (int i=1;i<=k;i++) cin>>s[i];
    		sort(s+1,s+1+k,cmp); // 按a_i从小到大
    		int ans=0;
    		for (int i=2;i<=k;i++){
    			ans+=p[s[i-1]].val-2*p[s[i]].val+c;
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    
    • 1

    信息

    ID
    7647
    时间
    1500ms
    内存
    512MiB
    难度
    3
    标签
    递交数
    0
    已通过
    0
    上传者