2.18+2.19考试总结

考了几场试,在此做个总结。


\(Day1\)

T1 家庭作业

题面

小y最近收到一个家庭作业,计算A和B的最大公约数。

由于这两个数太大了,我们给出了n个数,它们的乘积是A,给出m个数,它们的数是B。

输出这个最大公约数 mod 1000000000的值。

题解

题目大意:

有两个正整数,分别是 \(A=\prod \limits_{i=1}^n a_i\)\(B=\prod \limits_{j=1}^m b_i\),求 \(\gcd(A,B)\)


难度:三星(满分十星)。

看考虑非常显而易见的分解质因数。

首先对所有 \(a_i\) 进行分解质因数,将质因数放入桶 \(c\) 内(因为实际只需要考虑大小不超过 \(\sqrt {a_i}\) 的质数,所以普通线性筛足以)。

然后对 \(b_i\) 进行分解质因数,每分解出一个质因数 \(d\),就看看 \(c\) 中还有没有存货,有就 \(ans=ans\times d,c[d]--\)

注意取模 ( ^ _ ^ ),时间复杂度 \(O(n\sqrt M\log M)\)\(M\) 表示\(max(max(a_i),max(b_i))\),用 \(unordered\_map\) 可消除 \(\log\)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=1e5,M=1e9;map<ll,int>c;
int n,m,k,t[N];ll p[N],ans=1;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    for(ll i=2;i<N;i++){
        if(t[i]) continue;p[++k]=i;
        for(ll j=i*i;j<N;j+=i) t[j]=1;
    }cin>>n;while(n--){
        ll a;cin>>a;
        for(int i=1;i<=k&&p[i]*p[i]<=a;i++){
            while(a%p[i]==0)
                a/=p[i],c[p[i]]++;
        }if(a>1) c[a]++;
    }cin>>m;while(m--){
        ll b;cin>>b;
        for(int i=1;i<=k&&p[i]*p[i]<=b;i++){
            while(b%p[i]==0){
                b/=p[i];if(c[p[i]])
                    c[p[i]]--,ans=ans*p[i]%M;
            }
        }if(c[b]) c[b]--,ans=ans*b%M;
    }cout<<ans;return 0;
}

T2 距离之和

题面

想象一个机器人位于二维空间。初始时,机器人在 \((0,0)\)。有4个命令 \(S,J,I,Z\)

具体的,如果机器人在 \((x,y)\),在收到 \(S\) 命令之后,移动到 \((x,y+1)\),收到 \(J\) 之后,移动到 \((x,y-1)\)\(I\) 命令之后移动到 \((x+1,y)\)\(Z\) 命令之后移动到 $(x-1,y)。

在这个二维空间有 \(n\) 个固定的点,在每个命令之后,每个固定点会计算自己与机器人的曼哈顿距离,然后返回这些距离的总和。

ps:两个点 \((x1,y1)\)\((x2,y2)\) 的曼哈顿 \(|x1-x2|+|y1-y2|\)

题解

题目大意:

\(n\) 个点 \((x_i,y_i)\),现在一个机器人从 \((0,0)\) 出发,每次向上/下/左/右走一个单位长度,每次移动后询问机器人距离 \(n\) 的曼哈顿距离之和。


难度:四星(满分十星)

代码不难,主要难在最开始的思路。

我们似乎并没有快速求一个点距离 \(n\) 个点距离的算法,但是,我们是否有快速求解变化量的方法呢?


考虑简化版的问题:

  • 在一个数轴上,每个点有自己相应的点权 \(c_i\),机器人从原点出发,每次向左移动一个单位长度,求每次的 \(f(x)=\sum \limits_{i=0}^n c_i\times |x-i|\)

\(s_i=\sum \limits_{j=0}^i c_j\),那么我们会发现一个性质:

设原先在 点 \(i\),答案为 \(ans\),则现在答案为 \(ans+s_i-(s_n-s_i)\),即 \(ans+2s_i-s_n\)

证明显而易见,这里不再赘述。


运用这个性质,我们能做些什么呢?

考虑由于是曼哈顿距离,所以 竖直方向的移动不影响水平方向的距离,水平方向的运动不影响竖直方向的距离。 我们就可以将问题转化,分别在 \(x,y\) 两条坐标轴上建立上文所述的数轴即可。

举个例子,在 \(x\) 轴上的点 \(i\),其点权就是所有满足 \(x=i\) 的点数。

但是,这样转化出的数轴长度极长,容易 \(TLE/MLE/RE\)

我们发现大量的位置点权为零,因此可以直接将连续的点权为零的区间合并成一个大点存储,这样就最多只会有 \(2n+1\) 个点了。

代码时间复杂度瓶颈在于快排,常数极小,应该也是 \(oj\) 最优解,时间复杂度 \(O(n\log n)\),空间则是 \(O(n)\)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll INF=0x3f,N=1e5+5,M=2*N;
int n,m,h,l,a[M],b[M],s1[M],s2[M];
struct node{ll x,y;}e[N];string s;
ll ans,c[M],d[M];int nx,ny;
int cmp1(node z,node w){
    return z.x<w.x;
}int cmp2(node z,node w){
    return z.y<w.y;
}int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);cin>>n>>m;
    e[0].x=e[1].x=-INF;
    for(int i=1;i<=n;i++)
        cin>>e[i].x>>e[i].y;
    for(int i=1;i<=n;i++)
        ans+=abs(e[i].x)+abs(e[i].y);
    cin>>s;sort(e+1,e+n+1,cmp1);
    for(int i=1;i<=n;i++){
        if(e[i].x!=e[i-1].x){
            if(e[i].x-e[i-1].x>1)
                c[++h]=e[i-1].x+1;
            c[++h]=e[i].x;
        }a[h]++;
    }c[++h]=e[n].x+1;c[h+1]=INF;
    sort(e+1,e+n+1,cmp2);
    for(int i=1;i<=n;i++){
        if(e[i].y!=e[i-1].y){
            if(e[i].y-e[i-1].y>1)
                d[++l]=e[i-1].y+1;
            d[++l]=e[i].y;
        }b[l]++;
    }d[++l]=e[n].y+1;d[l+1]=INF;
    for(int i=1;i<=h;i++){
        s1[i]=s1[i-1]+a[i];
        if(c[i]<=0&&c[i+1]>0) nx=i;
    }for(int i=1;i<=l;i++){
        s2[i]=s2[i-1]+b[i];
        if(d[i]<=0&&d[i+1]>0) ny=i;
    }for(int i=0,x=0,y=0;i<m;i++){
        if(s[i]=='S'){
            ans+=s2[ny]*2-s2[l];
            y++;if(y>=d[ny+1]) ny++;
        }else if(s[i]=='J'){
            ans+=s2[l]-s2[ny-1]*2;
            y--;if(y<d[ny]) ny--;
        }else if(s[i]=='I'){
            ans+=s1[nx]*2-s1[h];
            x++;if(x>=c[nx+1]) nx++;
        }else{
            ans+=s1[h]-s1[nx-1]*2;
            x--;if(x<c[nx]) nx--;
        }cout<<ans<<"\n";
    }return 0;
}

我的代码难得细长苗条了些……

T3 country

BZOJ原题,在这里

题解

题目大意

原题:\(BZOJ\ 2061\)

给出 \(n\) 个以大写字母命名的字符串,字符串由其他大写字母或小写字母组成,每个大写字母应等效替换为他所命名的字符串,求最终以字符 \(c\) 命名的字符串中,包含多少个字符串 \(s\)


难度:五星半(满分十星)

开始差点以为是无脑大模拟……当然,直接暴力展开字符串肯定不行。

有两种做法,分别是 拓扑排序/记忆化搜索+\(kmp\)/\(hash\),个人觉得第二种更简单,想学 拓扑排序+\(kmp\) 的出门左拐找 \(dyc\),学 记忆化搜索+\(hash\) 的出门右拐找 \(wsq\),本文是 记忆化搜索+\(kmp\)


考虑既然是字符串匹配,那就要用 \(kmp\ or\ hash\)

既然有嵌套,那就能用 \(dfs\) 递归,那就能用记忆化搜索;既然有嵌套那就能建有向无环图,自然就可以用拓扑排序。

拓扑排序略显复杂(100+),\(hash\) 略显暴力,且相对于 \(kmp\),不易于 \(dp\) 结合,因此选择 记忆化搜索+\(kmp\)


定义 \(dp_{i,j}\) 表示处理到字符串 \(i\),上一次匹配到 \(j\) 位时的匹配次数。

遍历 \(i\) 中的字符:小写字母 \(kmp\) 并记录匹配到的位置 \(x\);大写字母设为 \(id\),递归 \(dfs(id,x)\)

当然,要记录数组 \(pos_{i,j}\),表示当前处理到字符串 \(i\),上一次匹配到 \(j\) 位,匹配结束后模板串的位置。递归完成后 \(x=pos_{id,x}\),同时更新 \(dp_{i,j}\)。遍历结束后,\(pos_{i,j}=x\)

假如求字符串 \(m\),那么答案为 \(dp_{m,0}\)

时间复杂度 \(O(nl^2)\)

代码

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int N=30,M=105,p=1e4;
int lt,n,m,nxt[M],a[N][M],len[M];
string s,t;int dp[N][M],pos[N][M];
int dfs(int x,int y){
	if(~dp[x][y]) return dp[x][y];
	dp[x][y]=0;int z=y;
	for(int i=1;i<=len[x];i++){
        if(a[x][i]<='Z'&&a[x][i]>='A'){
			int id=a[x][i]-'A'+1;
			dp[x][y]=(dp[x][y]+dfs(id,z))%p;
			z=pos[id][z];continue;
		}int q=z;while(q&&t[q+1]!=a[x][i]) q=nxt[q];
		if(t[q+1]==a[x][i]) q++;z=q;
		if(z==lt) dp[x][y]=(dp[x][y]+1)%p;
	}pos[x][y]=z;return dp[x][y];
}int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>s;m=s[0]-'A'+1;
	for(int i=1;i<=n;i++){
		cin>>s;lt=s.size();s=" "+s;
		for(int j=3;j<=lt;j++)
			a[s[1]-'A'+1][j-2]=s[j];
		len[i]=lt-2;
	}cin>>t;lt=t.size();t=" "+t;nxt[1]=0;
	for(int i=2,j=0;i<=lt;i++){
		while(j&&t[j+1]!=t[i]) j=nxt[j];
		if(t[j+1]==t[i]) j++;nxt[i]=j;
	}memset(dp,-1,sizeof(dp));dfs(m,0);
	cout<<dp[m][0];return 0;
}

T4 太空飞船

题面

Heavy为他的太空飞船设置了非常牛逼的密码,密码由 \(4\) 个正整数组成,已知它们互不相同,且最大公约数为 \(1\),Were显然不可能尝试所有的四元集。

幸运的,Were通过某种途径,将数的选择缩小至了一定范围内——一个含有 \(N\) 个数的集合,\(N\le10000\)

不过显然,它的四元子集还是太多了,Heavy外出的时间有限,Were必须在极短的时间内破译密码,成败在此一举!快帮他看看究竟有多少种可能的子集。

题解

题目大意:

给定 \(n\) 个正整数的数组 \(a\),需要求出满足 \(\gcd(a_i,a_j,a_k,a_l)=1\) 的四元组 \((i,j,k,l)\) 的个数。


难度:六星

暴力枚举不再赘述,\(60\)\(dp\) 详见老师题解,这里主要讲容斥正解。

发现:满足条件四元组=所有四元组-不满足条件四元组,已经是容斥无疑。

答案即为:\(\gcd\%1=0\) 的个数-\(\gcd\%2=0\) 的个数-\(\gcd\%3=0\) 的个数-……+\(\gcd\%(2\times 3)=0\) 的个数+\(\gcd\%(2\times 5)=0\) 的个数+……

发现难以统计答案,考虑直接枚举 \([1,10^4]\) 间的所有数,即可获取稳定时间复杂度。

当然,假如一个数 \(a\),对于质数 \(p\)\(a%(p^2)=0\),那么它将不能参与容斥。

我们只需要计算出以下几个数:

\[c_i=\sum \limits_{j=1}^n (a_j\% i==0) \]

\[v_i=\begin{cases} 0,i=prime\\1,\sum \limits_{j=prime}^{10^4}(a\%(j^2)==0)=0\\2,\sum \limits_{j=prime}^{10^4}(a\%(j^2)==0)>0\end{cases} \]

\[t_i=\sum \limits_{j=prime}^{10^4}(a\%(j^2)==0) \]

就可以求出答案,详见代码(时间复杂度 \(O(n\sqrt M)\)\(M\)\(max(a_i)\)):

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=10005;
int n,m,t[N],p[N],v[N];ll ans,c[N];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);cin>>n;
	for(int i=1;i<=n;i++){
		int a;cin>>a;
		for(int j=1;j*j<=a;j++)
			if(a%j==0) c[j]++,c[a/j]+=(j*j!=a);
	}for(int i=2;i<N;i++){
		if(!v[i]) p[++m]=i,t[i]=1;
		for(int j=1;i*p[j]<=10000;j++){
			if(i%p[j]&&v[i]!=2)
				v[i*p[j]]=1,t[i*p[j]]=t[i]+1;
			else v[i*p[j]]=2;
			if(i%p[j]==0) break;
		}
	}for(int i=1;i<=10000;i++){
		if(v[i]==2||c[i]<4) continue;
		ll re=c[i]*(c[i]-1)*(c[i]-2)*(c[i]-3);
		if(t[i]%2) ans-=re/24;else ans+=re/24;
	}cout<<ans;return 0;
}

\(Day2\)

T1 素数

题面

有一些正整数能够表示为一个或连续多个素数的和。

那么给定一些正整数,求有多少种这样的表示。

题解

题目大意:

给出多个正整数 \(x\),求有多少个二元组 \((l,r)\),满足 \(\sum \limits_{i=l}^{r}p_i=x\),其中 \(p_i\) 代表第 \(i\) 个素数。


难度:两星半(满分十星)

很好想到要将所有素数先算出来再说……

发现二元组满个单调性:

  • 如果 \((l,r)\) 满足要求,则 \((l+a,r-b)\) 一定不满足要求。其中 \(0<a\)\(0\le b\)

\(l,r\) 单调递增,双指针即可。

时间复杂度 \(O(pq)\)\(q\) 为询问次数,\(p\) 为小于 \(x\) 的素数个数,通常接近 \(\frac{x}{\log x}\)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=32770;
int m,a,ans[N],p[N],t[N];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	for(int i=1;i<N;i++) ans[i]=-1;
	for(int i=2;i<N;i++){
		if(t[i]) continue;p[++m]=i;
		for(int j=i*i;j<N;j+=i) t[j]++;
	}while(1){
		cin>>a;if(!a) break;
		if(ans[a]>=0){
			cout<<ans[a]<<"\n";
			continue;
		}ans[a]=0;
		int l,r=0,sum=0;
		for(l=1;l<=m;l++){
			if(p[l]>a) break;
			while(sum<a)
				sum+=p[++r];
			if(sum==a) ans[a]++;
			sum-=p[l];
		}cout<<ans[a]<<"\n";
	}return 0;
}

T2 晨练

题面

\(Y\) 打算通过锻炼来培养自己的运动细胞,他选择的运动方式是每天进行 \(N\) 分钟的晨跑 \((1\le N\le 10000)\)

在每分钟的开始,小 \(Y\) 会选择下一分钟是用来跑步还是休息。

\(Y\) 的体力限制了他跑步的距离。

更具体地,如果小 \(Y\) 选择在第 \(i\) 分钟内跑步,他可以在这一分钟内跑 \(D_i(1\le D_i\le 1000)\) 米,并且他的疲劳度会增加 \(1\)

不过,无论何时小 \(Y\) 的疲劳度都不能超过 \(M(1\le M\le 500)\)

如果小 \(Y\) 选择休息,那么他的疲劳度就会每分钟减少 \(1\),但他必须休息到疲劳度恢复到 \(0\) 为止。

在疲劳度为 \(0\) 时休息的话,疲劳度不会再变动。晨跑开始时,小 \(Y\) 的疲劳度为 \(0\)

还有,在 \(N\) 分钟的锻炼结束时,小 \(Y\) 的疲劳度也必须恢复到 \(0\),否则他将没有足够的精力来对付这一整天中剩下的事情。

请你计算一下,小 \(Y\) 最多能跑多少米。

题解

题目大意:

\(n\) 个单位时间。在第 \(i\) 个单位时间内,若选择跑步,疲劳度 \(+\ 1\),距离 \(+\ D_i\);若选择休息,疲劳度每单位时间 \(-\ 1\),且必须到 \(0\) 才能选择跑步,求最终疲劳度为 \(0\) 时,最长能跑多少。


难度:三星半(满分十星)

显而易见,可以用 \(dp\) 快速解决。

我们设状态 \(dp_{i,j}\) 表示现在在第 \(i\) 个单位时间,疲劳度为 \(j\) 时的最大跑步长度,开始只有 \(dp_{0,0}\)\(0\)

发现疲劳度为 \(0\) 时比较特殊,因此考虑分类讨论。有:

\[dp_{i,j}=\begin{cases}dp_{i-1,j-1}+D_i,j>0\\\max(dp_{i-1,j},dp_{i-p,j+p}),j=0\end{cases} \]

时间复杂度 \(O(nm)\)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=10005,M=505;
int n,m,dp[N][M],d[N];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>d[i];
	for(int i=1;i<=m;i++) dp[0][i]=-1e9;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++)
			dp[i][j]=dp[i-1][j-1]+d[i];
		dp[i][0]=dp[i-1][0];
		for(int j=i-1,k=1;j&&k<=m;j--,k++)
			dp[i][0]=max(dp[i][0],dp[j][k]);
	}cout<<dp[n][0];return 0;
}

T3 奇怪的桌子

题面

小H有一张奇怪的矩形桌子,他能在桌子上的格子内画点,一个格子最多只能画一个点。
这个桌子是 \(N\times M\) 的,现在小H想知道有多少种不同的放法能使得桌子上每个 \(N*N\) 的正方形内恰好有 \(K(0\le K\le N^2)\) 个点。

小H智商有限,所以他希望你能帮他解决这个问题。因为方案数可能有很多,所以你只需要输出方案数除以 \(1000000007\) 的余数。

题解

题目大意:

在一张 \(n\times m\) 的长方形中,在一些格子中放点,询问有多少种方案,使得每个 \(n\times n\) 的正方形中有且仅有 \(k\) 个点。


题目难度:五星半(满分十星)

要解决这个问题,我们需要从一道小学课内题讲起(说句闲话,作为一道小学数学老师讲过的题,我没有理由作不出来,在此向我的小学数学老师表达尊敬与愧疚)。


  • 填一个给定前 \(m-1\) 个数,长度为 \(n\) 的数列 \(a\),要求所有长度为 \(m\) 的区间,区间和都为 \(k\)

看起来有点难度,但是我们很快就可以发现一个规律:\(a_i=a_{i+m}\)

证明:有 \(\sum \limits_{j=i}^{i+m-1}a_j=\sum \limits_{k=i+1}^{i+m}a_k\)

消去相同量,得 \(a_i=a_{i+m}\)


现在,回到本题。(注:下文 \({y \choose x}=C_y^x=C(y,x)\)

发现每一列的点数 \(l\) 才是我们需要关心的,方案数即 \({n\choose l}\)

根据刚才提到的那个问题,轻而易举地发现每列的点数会形成一个长度为 \(n\) 的循环节。问题缩小到列数为 \(n\) 的情况。

发现可以用可爱的 \(dp\) 来解决这个问题。

设状态 \(dp_{i,j}\) 表示第 \(i\) 列时,已经填入 \(j\) 个点。显然有转移方程:

\[dp_{i,j}=\sum \limits_{l=0}^{\min(j,n)}dp_{i-1,j-l}\times{n\choose l}^{\frac{m}{n}+(i\le m\% n)} \]

初始 \(dp_{0,0}=1\)

这样计算,时间复杂度为 \(O(n^2k\log_2n)\),显然超时,因此在计算 \(C\) 值时,同时要预处理快速幂。时间复杂度 \(O(n^2k)\)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=105,K=N*N,p=1e9+7;
int n,k;ll m,dp[N][K],c[2][N][N];
ll qpow(ll x,ll y){
    ll re=1;while(y){
        if(y&1) re=re*x%p;
        x=x*x%p;y>>=1;
    }return re;
}int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m>>k;dp[0][0]=1;
    for(int i=0;i<=n;i++) c[0][i][0]=1;
    for(int i=1;i<=n;i++) c[0][i][i]=1;
    for(int i=2;i<=n;i++)
        for(int j=1;j<i;j++)
            c[0][i][j]=(c[0][i-1][j]+c[0][i-1][j-1])%p;
    for(int i=0;i<=n;i++){
        c[1][n][i]=qpow(c[0][n][i],m/n+1);
        c[0][n][i]=qpow(c[0][n][i],m/n);
    }for(int i=1;i<=n;i++)
        for(int j=0;j<=min(i*n,k);j++)
            for(int l=0;l<=min(n,j);l++)
                dp[i][j]=(dp[i][j]+dp[i-1][j-l]*c[(i<=m%n)][n][l]%p)%p;
    cout<<dp[n][k];return 0;
}

T4 学校

题面

众所周知,HXY家离学校很远。于是,HXY每天算准了时间出发,以保证能在上课铃响前 \(10^{-1000000}\) 秒到达学校。

不幸的是,CZ市最近正在修路。这就导致有些路可能无法通行,因而可能导致HXY迟到。

HXY不打算改变他的出发时间,现在他告诉你他通过每一条路的时间,他想要知道如果某条路被维修了,那么他是否能避免迟到?

题解

题目大意:

给出一张图、起点和终点,给出 \(q\) 个询问,每次询问删除第 \(c\) 条边时,最短路长度是否变化,变化输出 \(No\),否则输出 \(Yes\)


难度:六星(满分十星)

\(ps.\) 考试的时候愚蠢了,想到了 \(dij\)\(dfs\) 求图,却没想到 \(tarjan\) 求桥。悲兮!叹兮!(桥即割边)

既然是最短路,不想 \(AC\ to\ TLE\) 的小盆友们一定已经打好了 \(dij\) 的模板。但是,假如每次查询暴力 \(dij\) ,一定会得到 \(30pts\) 的高分。

那么,我们当然更希望只用一次 \(dij\) ,就完成对所有查询的回答。

首先我们发现,假如这条边不在从 \(s\)\(t\) 的所有最短路径上,那么他就不会影响最段路,一定输出 \(Yes\)。我们可以在 \(dij\) 时记录下每个点所有最短路径的最后一条边,并且用 \(dfs\) 倒着从 \(t\) 开始跑。因为只记录从 \(s\) 到其他点路径的反边,所以一定可以从重点到达起点。

进行完两次操作后,我们留下了一个仅包含所有从 \(s\)\(t\) 的最短路径的图。我们发现,当该边是桥时,他是不可或缺的,否则一定存在一条路径可以避开他。 证明显而易见,这里不再赘述。

求桥可以用 \(tarjan\) 轻松解决。

时间复杂度瓶颈为 \(dij\),故时间复杂度 \(O((m+n)\log n)\)

代码

//注:tarjan中的edge表示他来时的边,不然会WA掉原先的6.in
//感谢大家对我难得的不压行的肯定,我会继续努力的(^_^)
#include<bits/stdc++.h>
#define ll long long
#define P pair<int,int>
#define mp make_pair
#define ct first
#define nd second
using namespace std;
const int N=4e4+5,M=2e5+5;int d[N],vv[N];
int n,m,p,s,t,id,low[N],dfn[N],vis[M];
priority_queue<P,vector<P>,greater<P> >q;
vector<int>g[N],w[N],ii[N],l[N],jj[N];
void dij(int s){
	for(int i=1;i<=n;i++) d[i]=1e9;
	d[s]=0;q.push(mp(0,s));
	while(!q.empty()){
		P x=q.top();int u=x.nd;
		q.pop();if(x.ct>d[u]) continue;
		for(int i=0;i<g[u].size();i++){
			int v=g[u][i],cs=w[u][i];
            if(d[v]<cs+d[u]) continue;
			if(d[v]>cs+d[u]){
				l[v].clear();
                jj[v].clear();
				d[v]=cs+d[u];
                q.push(mp(d[v],v));
			}l[v].push_back(u);
            jj[v].push_back(ii[u][i]);
		}
	}
}void dfs(int x){
    if(vv[x]) return;vv[x]=1;
	for(int i=0;i<l[x].size();i++){
        int y=l[x][i];dfs(y);
        g[x].push_back(y);
        g[y].push_back(x);
        ii[x].push_back(jj[x][i]);
        ii[y].push_back(jj[x][i]);
	}
}void tarjan(int u,int edge){
    low[u]=dfn[u]=++id;
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(!dfn[v]){
            tarjan(v,ii[u][i]);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u])
                vis[ii[u][i]]++;
        }else if(ii[u][i]!=edge)
            low[u]=min(low[u],dfn[v]);
    }
}map<pair<P,int>,int>lyh;int a[M];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m>>s>>t;
    for(int i=1;i<=m;i++){
        int a,b,c;cin>>a>>b>>c;
        g[a].push_back(b);
        g[b].push_back(a);
        w[a].push_back(c);
        w[b].push_back(c);
        ii[a].push_back(i);
        ii[b].push_back(i);
    }dij(s);for(int i=1;i<=n;i++)
        g[i].clear(),ii[i].clear();
    dfs(t);tarjan(s,0);
    cin>>p;while(p--){
        int x;cin>>x;
        if(vis[x]>0) cout<<"No\n";
        else cout<<"Yes\n";
    }return 0;
}
posted @ 2024-02-19 21:33  长安一片月_22  阅读(17)  评论(0编辑  收藏  举报