2022.3.26训练(2020 ICPC上海 已有10/13)

目录

G题        数学,签到

B题        构造,思维题,基础

M题        模拟,树,基础

D题        二分答案,基础

H题        构造,中档

E题        dp优化,组合,中档

I题        几何,组合计数,中档

L题        构造,几何,中档

C题        数位dp,中档

K题        lca,dfs,较难

F题        数学,dp,较难



比赛现场榜单:Board - XCPCIO

所有题目链接:Dashboard - 2020 ICPC Shanghai Site - Codeforces

铜牌题:G,        B,        M,        D

银牌题:H,        E,        I,        L,        C

金牌题:K,        F,        A,        J

F,A,J三题尚未完成


G题        数学,签到

G题链接

题意:

f为斐波那契数列,求\sum_{i=1}^{n}\sum_{j=i+1}^{n}g(f_i,f_j),其中g(x,y)=\begin{cases} 1,x\times y \ is\ even \\ 0,otherwise \end{cases}

分析:

注意到斐波那契数列中第3k+1项和第3k+2项是奇数,3k项是偶数。

则斐波那契数列前n项中,有m=2\left \lfloor \frac{n}{3} \right \rfloor+n\ mod\ 3个奇数

所以答案为C_{n}^{2}-C_{m}^{2}

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long

signed main() {
	int n; cin >> n;
	int m = (n / 3);
	m = m * 2 + n % 3;
	cout << n * (n - 1) / 2 - m * (m - 1) / 2;
}

B题        构造,思维题,基础

B题链接

题意:

给出n\times m扫雷地图A和B,一个地图的权值定义为所有非雷块的权值之和,每个非雷块的权值定义为以它为中心的九宫格的雷块个数之和。要求修改B中不超过\left \lfloor \frac{mn}{2} \right \rfloor个块,使得A和B的权值一样多,并输出修改后的B

分析:

本题关键在于注意到A和A的相反图的权值总和相同,其中相反图定义为A中所有的雷块变为非雷块,非雷块变为雷块。

证明:图的权值实质上是如下“方块对”的数量——即两个方块在同一个九宫格内,而一个为雷块一个为非雷块。所以雷块和非雷块翻转后不影响总的权值和。

比较A和B不同的点的数量,记为cnt。若cnt\le \left \lfloor \frac{mn}{2} \right \rfloor,直接输出A。否则,输出A的相反矩阵

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long

char a[1005][1005], b[1005][1005];

signed main() {
	int n, m;
	cin >> n >> m;
	int cnt=0;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> a[i][j];
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> b[i][j];
			if (a[i][j] != b[i][j])
				cnt++;
		}
	}
	if (cnt <= n * m / 2) {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				cout << a[i][j];
			}
			cout << '\n';
		}
	}
	else {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				if (a[i][j] == 'X')cout << '.';
				else cout << 'X';
			}
			cout << endl;
		}
	}
	return 0;
}

M题        模拟,树,基础

Problem - M - Codeforces

题意:

有一个树形文件系统。输入n+m行文件路径,格式为:slash1/slash2/.../file,slash为文件夹,file为文件,保证输入的路径一定是以某个文件结尾(叶子结点)。

可以选择某些文件或者文件夹删掉。如果删掉一个文件夹,会把它包含的文件夹和文件全部删掉。

前n行路径表示的文件需要被删掉,后m行路径中的文件不能被删掉。

求删掉的文件或文件夹的最少数量。

分析:

给出的一个文件路径就是树上从根节点到叶子结点的一条路径。

m个不能删掉的节连同它们的祖先构成了一个子图G。把G上的所有节点打上标记flag。

对于每一个需要删掉的点,从上向下遍历它第一个没有flag标记的祖先(直接遍历它的文件路径即可),并将它删掉。可以把已经删掉的点也打一个删除标记vis,避免重复删除。

特别的,本题只需要保存每个节点的路径,而不需要特别建树。因为本题只需要从下向上搜索祖先,而每个路径都包含着所有祖先节点的信息。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
map<string, int> flag, vis;
int n, m;

signed main() {
	int t; cin >> t;
	while (t--) {
		string node[200];
		flag.clear();//用来标记某个节点是否被保留
		vis.clear();//用来标记某个节点是否已经被删掉
		cin >> n >> m;

		//储存需要删除的所有叶子结点
		for (int i = 1; i <= n; i++) {
			cin >> node[i];
		}

		//将所有需要保留的节点打上标记flag
		for (int i = 1; i <= m; i++) {
			string s;
			cin >> s;
			for (int j = 0; j < s.size(); j++) {
				if (s[j] == '/') {
					flag[s.substr(0, j)] = 1;
				}
			}
		}

		//对于每个需要删掉的叶子节点,从根向下找到它第一个没有打上标记的祖先节点
		//无论该节点是否已被删掉,都打上删掉的标记,并退出
		int ans = 0;
		for (int i = 1; i <= n; i++) {
			int tag = 1;
			for (int j = 0; j <= node[i].size(); j++) {
				if (node[i][j] == '/') {
					string ss = node[i].substr(0, j);//所有结束在'/'之前的前缀都是node[i]的祖先节点
					if (flag[ss]) tag = 1;//该祖先需要被保留
					else {//找到第一个不需要保留的祖先
						if (vis[ss]) tag = 0;//如果该祖先节点已经被删掉,那这次就不需要删了
						vis[ss] = 1;
						break;
					}
				}
			}
			if (tag) ans++;
		}
		cout << ans << endl;
	}
	return 0;
}

D题        二分答案,基础

Problem - D - Codeforces

题意:

给一个长度为n的线段。两个人给定初始位置p_1, \ p_2和速度v_1v_2,方向可随时随意改变。问最短多久两个人能遍历整个线段。

分析:

设左边的人位置为p_1,右边的人位置为p_2,两个人将线段可分为三部分x_1+x_m+x_2,其中x_1=p_1,\ x_2=n-p_2

二分答案,对于时间t求出两个人能走的距离 s_1 和 s_2

若其中一个人就能走完全程,则直接返回true;若某个人到达距离自己较近的点,那么另一个人必须走完全程,而该情况已经排除,所以直接返回false;

于是剩下的情况中,两个人都有能力到达距离自己较近的边界,但都不能独自走完全程。

此时求左边的人能到达的最右端r,和右边的人能到达的最左端l。以左边的人为例,可能先向左到达端点再向右超过出发点(r=s_1-p_1),或先向左到达端点但是不足以回到出发点(r=p1),或先向右到达某个距离再向左到达端点(2r-p_1=s_1),对r取最大值即可,l 同理。若r-l>eps则可认为能够相遇(覆盖全程)

代码(copy lhf):

#include <bits/stdc++.h>
#include <array>
using namespace std;
#define int long long
typedef unsigned long long ull;
typedef unsigned int uint;
int t = 1;
const int mod = 998244353;
const int inf = 1e12;
const int N = 2e5 + 5, M = 2e5 + 5; 
const long double eps = 1e-10;
long double n, p1, p2, v1, v2; 

bool check(long double t) {
	long double s1 = v1 * t, s2 = v2 * t;
	if (s1 >= n + min(p1, n - p1) || s2 >= n + min(p2, n - p2))
        return true; //某个人独自走完全程
	if (s1 < p1 || s2 < n - p2)
        return false;//有一个人不能到达离自己较近的边界
	long double l, r;
	r = max(s1 - p1, 0.5 * (s1 + p1));
	l = min(n - (s2 - (n - p2)), n - (0.5 * (s2 + n - p2)));
	r = max(r, p1);
	l = min(l, p2);
	if (r - l >= eps)return true; 
	else return false;
}

void init() {
}

void solve(){
	cin >> n >> p1 >> v1 >> p2 >> v2;
	if (p1 > p2)swap(p1, p2), swap(v1, v2);
	long double L = 0.0, R = max((n - p1) / v1, p2 / v2);
	while (R - L >= eps) {
		long double mid = 0.5 * (L + R);
		if (check(mid))R = mid;
		else L = mid;
	}
	cout << fixed << setprecision(10) << 0.5 * (L + R) << "\n";
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> t;
	while (t--) {
		init();
		solve();
	}
	return 0;
}

H题        构造,中档

Problem - H - Codeforces

题意:

有一个圆形的桌子,共有n个点。有k个客人坐在桌旁,k盘菜放在桌上,位置已知。可以顺时针或逆时针转动桌子,使得所有的菜一起转动。为了使每个客人都有一盘菜,求转动的最小距离

分析:

先把人和菜排序,方便处理。

最终每个人对应每一道菜。如果人i在j的顺时针方向,则i的菜也一定在j的顺时针方向(贪心即可证明),也就是说所有人和菜的连线不会有交点。因此只要第一个人对应了某道菜,则所有的人对应的菜就确定了。

于是枚举第一个人对应的菜,共k种情况。

对于每一种情况可先预处理每个人要想拿到自己的菜需要顺时针转动的距离。

可以贪心证明转动方向的改变次数不会多余1次:若先向右转,再向左转,再向右转,则第一次向右转一定是多余的。

于是只有四种方式满足所有人:一直顺时针转下去直到每个人拿到自己的菜;一直顺时针转下去直到每个人拿到自己的菜;先顺时针转到某个人i拿到自己的菜,再逆时针转让其他人拿到菜;先逆时针转到某个人i拿到菜,再逆时针转让其他人拿到菜。枚举i即可。

代码:

#include<bits/stdc++.h>
using namespace std;

#define ll long long
const int maxn = 1e5 ;
ll a[maxn], b[maxn], dis[maxn];
int main() {
    int t;cin>>t;
    while (t--) {
        ll n, k;cin>>n>>k;
        for(int i=0;i<=k-1;i++) cin>>a[i];
        for(int i=0;i<=k-1;i++) cin>>b[i];
        sort(a, a + k);
        sort(b, b + k);
        ll ans = n*100;
        for (int rot = 0; rot <= k - 1;rot++) {
            for(int i=0;i<=k-1;i++) dis[i] = (b[(i + rot) % k] - a[i] + n) % n;
            sort(dis, dis + k);
            ans = min({ ans, dis[k - 1], n - dis[0] });
            for (int i = 0; i <= k - 2;i++) {
                ans = min(ans, 2 * dis[i] + (n - dis[i + 1]));
                ans = min(ans, 2 * (n - dis[i + 1]) + dis[i]);
            }
        }
        cout<<ans<<'\n';
    }
}

E题        dp优化,组合,中档

Problem - E - Codeforces

题意:

给定1<k<n,满足以下性质的1~n的排列a称为“好排列”:

\forall k<i\leqslant n,\ a_i>min\left \{ a_{i-k},a_{i-k+1},...,a_{i-1} \right \}

求好排列的个数

分析:

对于一个包含m个数字的“好序列”p,设其中最小的数字是p_i ;

m\le k,则包含m个数字的好序列有m! 种;

m>k只要 i \leqslant k,且后n-i个数字组成的序列是“好序列”,那么原序列是好序列。证明:思路是对于\forall k<j\leqslant m,证明a_j 满足定义。由于后m-i个数字的序列是好序列,则对于j>i+k,一定满足原定义要求;而对于\forall k<j\leqslant i+k,有\ j-k\leq i\leqslant j-1,而a_i<a_j一定成立,因此也满足原定义要求。综上证毕。

dp[m]表示长度为m的序列的所有排列中,好序列的个数。由上述性质,只需要枚举最小元素的位置i,安排好前i-1个元素,再安排后n-i个元素组成的好序列。

dp[m]=\begin{cases} m!,\ 1\le m\le k \\ \sum_{i=1}^{k}A_{m-1}^{i-1}dp_{m-i},\ k<m<n \end{cases},但是暴力转移复杂度O(nm);

继续化简:\sum_{i=1}^{k}A_{m-1}^{i-1}dp_{m-i}  =\sum_{i=1}^{k}\frac{(m-1)!dp_{m-i}}{(m-i)!}  =(m-1)!\sum_{i=1}^{k}\frac{dp_{m-i}}{(m-i)!}

维护  pre(m)=\sum_{i=1}^{m}\frac{dp_{m-i}}{(m-i)!}

则  dp[m]=(m-1)!\times (pre[m-1]-pre[m-k-1])

pre(m)=pre(m-1)+\frac{dp_{m-1}}{(m-1)!}

O(n)就可以实现所有转移。

注意先预处理逆元

代码(参考网络):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e7 + 5;
const ll mod = 998244353;
ll dp[maxn], pre[maxn], fac[maxn], inv[maxn];

ll powmod(ll a, ll b) {//快速幂
    ll ans = 1;
    while (b) {
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

void init() {//处理阶乘,
    fac[1] = fac[0] = 1;
    for (int i = 2; i < maxn; ++i) fac[i] = fac[i - 1] * i % mod;
    inv[maxn - 1] = powmod(fac[maxn - 1], mod - 2);
    for (int i = maxn - 2; i >= 0; --i)
        inv[i] = inv[i + 1] * (i + 1) % mod;
}

signed main() {
    ll n, k;
    cin >> n >> k;
    init();
    dp[1] = 1, pre[1] = 2, pre[0] = 1;
    for (int m = 2; m <= n; ++m) {
        if (m <= k)
            dp[m] = fac[m-1] * pre[m - 1] % mod;
        else
            dp[m] = fac[m-1] * (pre[m - 1] - pre[m - k - 1] + mod) % mod;
        pre[m] = pre[m - 1] + dp[m] * inv[m] % mod;
        if (pre[m] > mod) pre[m] -= mod;
    }
    cout << dp[n] << endl;
}

I题        几何,组合计数,中档

Problem - I - Codeforces

题意:

有n个圆,全部以(0,0)为圆心,第i个圆半径为i。有m条直线,每条直线都经过原点,这m条直线把每个圆都分成均匀的2m份。求图中所有的点对(a,b)的距离和。

分析:

若两个点在同一个圆上,则它们之间的距离为直径和劣弧的较小者;若两个点不在一个圆上,则外层点向内走到内层点的圆上,再加上同圆上两点距离;

a[i]表示 第i 个圆上某点,到 其内部 i-1个圆 以及 第i 个圆上 所有点 的距离和;

b[i]表示 第i 个圆上某点,到 这个圆上所有点 的距离和;

由相似,b[i]=i\times b[1]

a[i] 由两部分组成:第i个圆上的点之间的距离和b[i] ,以及第i个圆到其内部的圆上的节点的距离和。至于第二部分,是第i 个圆上的点先往里走一步到第i-1个圆上,然后再加上a[i-1] ,由于内部有2m(i-1) 个点,所以要往里走2m(i-1) 次。综上,a[i]=b[i]+2m(i-1)+a[i-1]

求和的时候从内向外枚举每个圆,枚举到第i 个圆就在ans 里加上第i 个圆上的点到内部i-1个圆上的点的距离和2m(a[i]-b[i]),再加上第i 个圆上所有不同两点的距离之和 m\times b[i] 

注意,如果m>1,则直线在圆心处也有交点。此时需要加上圆上所有点到圆心的距离

代码:

#include<bits/stdc++.h>
using namespace std;
const int mx = 5001;
const double pi = acos(-1);
double a[mx], b[mx];

int main() {
	int n, m; cin >> n >> m;
	double cnt = 0;

	for (int i = 1; i < m; i++) {
		if (pi * i < 2 * m) {
			cnt += pi * i / m;
		}
		else {
			cnt += 2;
		}
	}
	cnt *= 2; cnt += 2;//现在的cnt是第一个圆上的某一点到该圆所有点的距离和

    //a[i],b[i]意义如上分析
	a[1] = b[1] = cnt;
	for (int i = 2; i <= n; i++) {
		b[i] = b[1] * i;
		a[i] = b[i] + a[i - 1] + (i - 1) * 2 * m;	
	}

	double ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += 2 * m * (a[i] - b[i]) + m * b[i];
		//如果m大于1说明原点位置有交点,要求出每个圆上的点到原点的距离和。		
		if (m > 1) 
			ans += 2 * m * i ;
	}
	printf("%.10lf\n", ans);
	return 0;
}

L题        构造,几何,中档

Problem - L - Codeforces

题意:

有一个(n+1)\times (m+1) 的网格图,现在要从(0,0)点走到(n,m)点,满足每次只能走到一个整点,路径为两点连成的直线段,这条线段上不能有其他整点,两点之间的路程即这条线段的长度(欧几里得距离),且不能连续两次往同一方向走,问最短的路程。
1\le m,n\le 10^{6}

分析:

gcd(n,m)=1,则直接一步到达;

否则,设直线l 为从起点到终点的对角线,设dis为两点间的欧几里得距离;从1~m枚举横坐标x,求最大的y满足点(x,y)在 l 下方,则ans=min\left \{ dis(O,P)+dis(P,T) \right \}

下面证明:为什么一定是两条折线而不是更多,为什么是最大的y,是否会经过整点

首先,路线最多由两条路线组成。如下图,只要是三条折线,一定会被“三角形任意两边大于第三边”被优化掉,如下图OP_1P_2T的长度一定大于 OP_2T 和 OP_1T 。至于是否会经过整点归到后面讨论。

(我自己画的图,画的比例有点夸张,而且是个梯形)

 其次,一定要选在直线l 下的最上方的点,如图,|OP_1T|<|OP_2T| 。原因是|OP_2>OP_1|,\ \ |P_2T>P_1T| , 则相加不等号不变。

最后,如果某条折线上面有整点,那么这种情况一定不是最优情况,例如枚举x得到点P后,发现PT上面有一个整点,那么OP+PT一定不是最优解,因为显然OQ+QT>OP+PT。因此只要折线上有整点,那么另一整点一定会将这个折线优化掉。因此最小的折线上一定没有整点。

 代码:

#include <bits/stdc++.h>
using namespace std;

#define dis(x, y) sqrt(1.0 * (x) * (x) + 1.0 * (y) * (y))
using namespace std;
typedef long long ll;
int T, n, m;

int main() {
    scanf("%d", &T);
    while (T--) {
        cin>>n>>m;
        if (__gcd(n, m) == 1) {
            printf("%.10lf\n", dis(n, m));
            continue;
        }
        double ans = 1e18;
        if (n > m) swap(n, m);
        for (int i = 1, j; i <= n; i++) {
            j = (1ll * m * i - 1) / n;
            ans = min(ans, dis(i, j) + dis(n - i, m - j));
        }
        printf("%.10lf\n", ans);
    }
    return 0;
}

C题        数位dp,中档

Problem - C - Codeforces

题意:

X,Y,求 \sum_{i=0}^{X}\sum_{j=\left [ i=0 \right ] }^{Y}\left [ i\& y=0 \right ] \left \lfloor log_2 (i+j)+1 \right \rfloor 的值,答案对10^9 +7 取模

分析:

由于i\& j=0,两个数字二进制每一位最多不可能同为1,因此i+j不会产生进位;因此\left \lfloor log_2 (i+j)+1 \right \rfloor 的含义是i 和j 中最高位的1的位置,可以用数位dp做;

【状态表示】

len 是 i 和 j 的最高位1的位置;

a 和 b 分别表示 i 是否等于X,j 是否等于Y;

综上dp[len][a][b] 的含义为满足以下条件的(i,j) 个数:

①i和j二进制最高位是len,即i和j的二进制高于len的数位都是0;

②a==[i==X], b==[j==Y];

③ i&j==0;

设X的二进制有 lenX 位,Y的二进制有 lenY位,则所求答案为:

\sum_{k=1}^{lenX}(dp[k-1][[k=lenX]][[k>lenb]] +\sum_{k=1}^{lenY}dp[k-1][[k>lenY]][[k=lenb]]

意思是所有先枚举,k求以i为最高位,且最高位是第k位的(i,j)数量;再枚举k,求以j为最高位,且最高位是第k位的(i,j)数量。

【状态转移】

将 最高位是第k位的状态 转移为 最高位为k-1的状态答案之和;即子状态的 len 一定是 k-1;

讨论子状态的a'和b':

首先a'和b'一定可以为0;

若a==1 并且X的第 k 位是1,则a'可以为1;

若b==1 并且Y的第 k 位是1,则b'可以为0;

具体的转移技巧见代码;

转移的时候k从大到小记忆化搜索即可;

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 1e9 + 7;
const int N = 50;
ll dp[N][2][2];

//dp[len][j][k]:从第len位开始数,i是否上限,j是否上限时i&j==0的个数 

int digitX[N], digitY[N];
ll a, b;

ll dfs(int len, bool limita, bool limitb) {
	if (len == 0) return 1;
	if (dp[len][limita][limitb] != -1) return dp[len][limita][limitb];

	int upa = limita ? digitX[len] : 1;
	int upb = limitb ? digitY[len] : 1;
	ll res = 0;
	for (int ii = 0; ii <= upa; ii++) {//ii是i的第k位,jj是j的第k位,为0或1
		for (int jj = 0; jj <= upb; jj++) {
			int x = ii & jj;
			if (x == 1) continue;
			res = (res + dfs(len - 1, limita && ii == upa, limitb && jj == upb)) % mod;		
		}
	}
	return dp[len][limita][limitb] = res;
}

ll cal(ll a, ll b) {
	memset(digitX, 0, sizeof digitX);
	memset(digitY, 0, sizeof digitY);
	int lenX = 0, lenY = 0;
	while (a) digitX[++lenX] = (a & 1), a >>= 1;
	while (b) digitY[++lenY] = (b & 1), b >>= 1;

	ll res = 0;
	memset(dp, -1, sizeof dp);
	for (int k = lenX; k >= 1; k--) {//i的最高位确定Log2(i+j)+1的值,即:i的第k位为1,j的第k位为0
		res += (dfs(k - 1, k == lenX, k > lenY) * k) % mod;
		res %= mod;
	}
	memset(dp, -1, sizeof dp);
	for (int k = lenY; k >= 1; k--) {//j的最高位确定Log2(i+j)+1的值,即:i的第k位为0,j的第k位为1
		res += (dfs(k - 1, k > lenX, k == lenY) * k) % mod;
		res %= mod;
	}
	return res;
}


int main(void) {
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%lld%lld", &a, &b);
		printf("%lld\n", cal(a, b));
	}
}

K题        lca,dfs,较难

Problem - K - Codeforces

题意:

无向图,每个节点有状态L或H,0号节点一定是L。从0出发,每到达一个节点u,u的状态将被翻转。要求不能连续经过两个状态相同的节点,并且无限走下去。问该图能否满足要求。

分析:

简称 “不连续经过相同状态节点 ”的路径为好路径,两端点初始状态不同的边为异色边。

该图能满足要求充要条件:存在一个点x,从起点有一条好路径到达x,且从x出发存在一条好路径能回到x,并且返回x的上一个节点的初始状态与x的初始状态相同,即x位于一个奇环。简而言之,从起点出发有一条好路径,终点x与该路径中某点u同色,并且x与u相连。

方法:

用异色边建图G。对于每条同色边(u,v)检查图G中是否存在0~u~v或0~v~u的简单路径,如果存在的话那么添加上这条同色边后就满足原要求;

判断是否存在上述简单路径的办法是:预处理图G的双连通分量,预处理图G的dfs树的所有lca。每次查询u,v时,设u,v的lca为w,若u,v,w在图G的同一个点双联通分量,则满足要求。

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 10;

int head[MAXN], ver[MAXN << 1], Next[MAXN << 1], tot;

inline void add(int x, int y) {
    ver[++tot] = y;
    Next[tot] = head[x];
    head[x] = tot;
}

struct E_DCC {//双联通分量分解
    int dfn[MAXN], low[MAXN], num, c[MAXN], dcc;
    bool bridge[MAXN << 1];

    inline void init(int n) {
        num = dcc = 0;
        memset(bridge, false, sizeof(bool) * (tot + 1));
        memset(dfn, 0, sizeof(int) * (n + 1));
        memset(c, 0, sizeof(int) * (n + 1));
        tarjan(1, 0);
        for (int i = 1; i <= n; i++)
            if (!c[i] && dfn[i])
                ++dcc, dfs(i);
    }

    void tarjan(int x, int in_edge) {
        dfn[x] = low[x] = ++num;
        for (int i = head[x]; i; i = Next[i]) {
            int y = ver[i];
            if (!dfn[y]) {
                tarjan(y, i);
                low[x] = min(low[x], low[y]);
                if (low[y] > dfn[x])
                    bridge[i] = bridge[i ^ 1] = true;
            }
            else if (i != (in_edge ^ 1))
                low[x] = min(low[x], dfn[y]);
        }
    }

    void dfs(int x) {
        c[x] = dcc;
        for (int i = head[x]; i; i = Next[i]) {
            int y = ver[i];
            if (c[y] || bridge[i]) continue;
            dfs(y);
        }
    }
} E;

struct LCA {//查询LCA
    int t, f[MAXN][20], d[MAXN];

    inline void init(int r, int n) {
        memset(d, 0, sizeof(int) * (n + 1));
        memset(f[1], 0, sizeof(f[1]));
        d[r] = 1, t = log2(n), dfs(r);
    }

    void dfs(int x) {
        for (int i = head[x], y; i; i = Next[i]) {
            if (d[y = ver[i]])continue;
            d[y] = d[x] + 1, f[y][0] = x;
            for (int j = 1; j <= t; j++)
                f[y][j] = f[f[y][j - 1]][j - 1];
            dfs(y);
        }
    }

    inline int lca(int x, int y) {
        if (d[x] > d[y])swap(x, y);
        for (int i = t; i >= 0; i--)
            if (d[f[y][i]] >= d[x])
                y = f[y][i];
        if (x == y)return x;
        for (int i = t; i >= 0; i--)
            if (f[x][i] != f[y][i])
                x = f[x][i], y = f[y][i];
        return f[x][0];
    }
} L;

int T, n, m;
vector<pair<int, int>> edge;
char c[MAXN];

inline bool check(int x, int y) {
    if (!E.dfn[x] || !E.dfn[y])return false;
    int lca = L.lca(x, y);
    if (lca == x || lca == y)return true;
    return E.c[L.f[lca][0]] == E.c[x] || E.c[L.f[lca][0]] == E.c[y];
}

int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        scanf("%s", c + 1);
        memset(head, 0, sizeof(int) * (n + 1));
        tot = 1, edge.clear();
        for (int i = 1, x, y; i <= m; i++) {
            scanf("%d%d", &x, &y), x++, y++;
            if (c[x] != c[y])add(x, y), add(y, x);
            else edge.emplace_back(x, y);
        }
        E.init(n), L.init(1, n);
        bool flag = false;
        for (auto e : edge)
            if (check(e.first, e.second)) {
                flag = true;
                break;
            }
        puts(flag ? "yes" : "no");
    }
    return 0;
}

F题        数学,dp,较难

题意:

又是特别长的一大段题干,读到最后面的Formally才发现前面都是废话……拿到题应该先扫一眼下面有没有Formally, In a word 这样的词;数学题就直接贴图片了。

分析:

先贴一下知乎上的一个分析,以后再写代码 

 

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值