CSP模拟赛题解

CSP模拟16#

T1 : 糖果#

这道题的思路很巧妙,明白了思路之后可以轻松切掉。既然这是求异或和,那根据异或的性质,如果是分为奇数段,那最后就会消为3段;如果是偶数段,最后会消为2段。特别要注意的是如果最后是两段的话,总的异或和一定为0。对于奇数段,先找到一个前缀的异或和,让它等于总的异或值,然后找到存不存在一个后缀异或和等于前缀(也就是总的异或和),然后这道题就可以轻松的切掉了……
代码实现:

点击查看代码
int n, temp1, temp2, T;
int a[100010], sum1[100010], sum2[100010];
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> T;
	while (T--) {
		memset(sum1, 0, sizeof(sum1));
		memset(sum2, 0, sizeof(sum2));
		temp1 = 100010; temp2 = 0;
		cin >> n;
		for (int i = 1; i <= n; i++) {
			cin >> a[i];
			sum1[i] = sum1[i-1] ^ a[i];
		}
		for (int i = n; i >= 1; i--)
			sum2[i] = sum2[i+1] ^ a[i];
		if (!sum1[n]) cout << "YES" << '\n';
		else {
			for (int i = 1; i <= n; i++)
				if (sum1[i] == sum1[n]) {
					temp1 = i;
					break;
				}
			for (int i = n; i >= 1; i--)
				if (sum2[i] == sum1[n]) {
					temp2 = i;
					break;
				}
			if (temp1 < temp2) cout << "YES" << '\n';
			else cout << "NO" << '\n';
		}
	}
	return 0;
}

CSP模拟17#

T1:弹珠游戏#

这道题要求最大值和最小值的和最小的情况,于是我们考虑贪心,也就是我们尽量让能拿全的人先全部拿全,这样才可以让当前发的弹珠发挥最大的作用,因此我们在考虑当前弹珠发挥的作用时,不需要考虑已经含有这种弹珠的人。
举个例子:
当前要发的弹珠是 R ,那么我们只需要去考虑 G,B,GB 和没有弹珠的人,优先考虑 GB ,其次是剩余的有弹珠的,最后是没有弹珠的。分类讨论即可。记得开 long long
代码实现:

点击查看代码
#define int long long
#define R 1
#define G 2
#define B 3
#define RB 4
#define RG 5
#define BG 6
using namespace std;
const int mod = 998244353;
int n, ans = 1;
int cnt[10];
char op;
signed main() {
	// freopen("../cin/a.txt","r",stdin);
	cin >> n; cnt[0] = n;
	for (int i = 1; i <= n*3; i++) {
		cin >> op;7
		if (op == 'R') {
			if (cnt[BG]) {
				ans = ans * cnt[BG] % mod;
				cnt[BG]--;
			} else if (cnt[G]) {
				ans = ans * cnt[G] % mod;
				cnt[G]--;
				cnt[RG]++;4/..0516602
			} else if (cnt[B]) {
				ans = ans * cnt[B] % mod;
				cnt[B]--;
				cnt[RB]++;
			} else {
				ans = ans * cnt[0] % mod;
				cnt[R]++;
				cnt[0]--;
			}
		}
		if (op == 'B') {
			if (cnt[RG]) {
				ans = ans * cnt[RG] % mod;
				cnt[RG]--;
			} else if (cnt[R]) {
				ans = ans * cnt [R] % mod;
				cnt[R]--;
				cnt[RB]++;
			} else if (cnt[G]) {
				ans = ans * cnt[G] % mod;
				cnt[G]--;
				cnt[BG]++;
			} else{
				ans = ans * cnt[0] % mod;
				cnt[0]--;
				cnt[B]++;
			}
		}
		if (op == 'G') {
			if (cnt[RB]) {
				ans = ans * cnt[RB] % mod;
				cnt[RB]--;
			} else if (cnt[R]) {
				ans = ans * cnt[R] % mod;
				cnt[R]--; cnt[RG]++;
			} else if (cnt[B]) {
				ans = ans * cnt[B] % mod;
				cnt[B]--; cnt[BG]++;
			} else {
				ans = ans * cnt[0] % mod;
				cnt[0]--; cnt[G]++;
			}
		}
	}
	cout << ans << '\n';
	return 0;
}

T2:晚会#

题目说的是让你去判断能否满足不存在“不友好”的关系,如果能满足,则输出所有人的之间的 C(i,j) 的和;不满足则输出 1 。题目定义“不友好”的关系是不让两个人之间的关系小于另外一个人与两人分别的关系,关键就在于想到怎么判断。
答案是用类似于 Kruskal最大生成树的思想,去实现这一过程。具体的,先将已知的边权按从大到小排序,然后一条条去判断,如果边连的两个点均出现在同一个连通块中(可以用并查集实现),需要判断这条边的权值是不是小于当前连通块的最小边权(主要是对于存在相同权值的边考虑),如果小于最小边权,那么就可以输出 1 了(因为是从大到小去加边,如果在一个连通块那么一定会出现“不友好”的关系)。如何求最后的答案,在从大到小判断时,如果是没在连通块里的点,那么连上这条边后,相当于给两连通块里的点都连了一条边, ans+=size[i]size[j]w(sizew),最后判断一下连通块之间的关系和即可(具体看代码实现)。记得开 long long
代码实现:

点击查看代码
struct node {
	int from, to, val;	
	friend bool operator <(node x1, node x2) {
		return x1.val > x2.val;
	}
} edge[M];
int n, m, ans, cnt;
int fa[M], vis[M], minn[M], size[M], bel[M];
int Find(int x) {
	if (x == fa[x]) return x;
	return fa[x] = Find(fa[x]);
}
signed main() {
	n = read(), m = read();
	for (int i = 1; i <= n; i++)
		fa[i] = i, size[i] = 1, minn[i] = 0x7f7f7f7f;
	for (int i = 1; i <= m; i++)
		edge[i].from = read(), edge[i].to = read(), edge[i].val = read();
	sort(edge + 1, edge + 1 + m);
	for (int i = 1; i <= m; i++) {
		int fx = Find(edge[i].from), fy = Find(edge[i].to), z=edge[i].val;
		if (fx != fy) {
			fa[fy] = fx;
			ans += size[fx] * size[fy] * z;
			minn[fx] = min(min(minn[fx], z), minn[fy]);
			size[fx] += size[fy];
		} else {
			if (z < minn[fx]) {
				write(-1); putchar('\n');
				return 0;
			}
			minn[fx] = min(minn[fx], z);
		}
	}
	for (int i = 1; i <= n; i++) {
		int fx = Find(i);
		if (vis[fx]) continue;
		bel[++cnt] = fx;
		vis[fx] = 1;
		for (int j = 1; j <= cnt - 1; j++)
			ans += size[bel[cnt]] * size[bel[j]];
	}
	write(ans); putchar('\n');
	return 0;
}

CSP模拟18#

T1:The Third Letter#

这是带权并查集的模板题,但是我不会。你可以让在前面的是在后面的祖先,也可以反过来,这都无所谓,主要是并查集的权值传递,我们用一个 d[i] 来存 i 到其祖先的距离,这个距离我们可以在查找祖先时递归更新实现,主要需要注意的就是两个连通块合并的时候,更新两个连通块祖先的距离(自己思考一下其实很简单)。对于出现环的情况,就需要判断存不存在重边不同权值,如果存在,记录一下,最后输出 NO ;否则,输出 YES 即可。记得开 long long
代码实现:

点击查看代码
int n, m, x, y, z, p, T;
int d[300010], f[300010];
int Find(int x) {
    if (x == f[x])
        return x;
    else {
        int fx = Find(f[x]);
        d[x] += d[f[x]];
        f[x] = fx;
        return f[x];
    }
}
signed main() {
    cin >> T;
    while (T--) {
        p = 0;
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
            d[i] = 0, f[i] = i;
        for (int i = 1; i <= m; i++) {
            cin >> x >> y >> z;
            if (p == 1)
                continue;
            int fx = Find(x), fy = Find(y);
            if (fx != fy) {
                f[fy] = fx;
                d[fy] = d[x] + z - d[y];
            } else {
                if (d[y] - d[x] != z) {
                    p = 1;
                }
            }
        }
        if (!p)
            cout << "YES" << '\n';
        else
            cout << "NO" << '\n';
    }
    return 0;
}

T2:Ina of the Mountain#

先咕了,放个代码先。
代码实现:

点击查看代码
priority_queue <int> q;
const int maxn = 1e9+10;
int n, k, ans, sum, T;
int a[200010];
signed main() {
	cin >> T;
	while (T--) {
		while (!q.empty()) q.pop();
		cin >> n >> k;
		for (int i = 1; i <= n; i++)
			cin >> a[i];
		sum = 0; ans = 0;
		for (int i = 1; i <= n; i++) {
			int A, B = (a[i] - a[i-1] + k) % k;
			if (!B) continue;
			A = B - k;
			if (sum + A >= 0) {
				sum += A;
				q.push(-B);
			} else {
				int mn;
				if (!q.empty()) mn = -q.top();
				else mn = maxn;
				if (mn < B) {
					sum += k + A; ans += mn;
					q.pop(); q.push(-B);
				} else {
					sum += B; ans += B;
				}
			}
		}
		cout << ans << '\n';
	}
	return 0;
}

CSP模拟19#

T1:Strange Function#

先观察数据范围,显然 O(nT) 复杂度会炸,那我们就考虑如何才能降低时间复杂度,我们来思考这个 f(i) 的性质,发现:如果 f(i)=k 说明 k 之前的数都可以整除 ik 不可以整除 i ,那也就是说, lcm(1,2,...,k1)iki ,那我们就可以通过枚举 k 的个数,来降低时间复杂度,使复杂度降到 O(Tlogn)

T2:DZY Loves Modification#

这道题就是利用了贪心的思想,可以使用优先队列存每一行和每一列的和,先分别对行和列进行处理,考虑到最后的答案是和选行或列的顺序无关的,行与行或列与列之间前后选择,互相没有影响,最后只需要减去选行选列的交点的个数乘 p 即可。具体实现方法就是在分别考虑完行和列的方案贡献后,最后枚举一次 k ,计算 i 次选行和 ki 次选列得出的答案,最后取最大值为 ans ,记得开 long long
代码实现:

点击查看代码
const int maxn = 1e18;
priority_queue<int>line, row;
int n, m, k, p, res, cntl, cntr, ans = -maxn;
int a[1010][1010], suml[1000010], sumr[1000100];
signed main() {
    cin >> n >> m >> k >> p;
    for (int i = 1; i <= n; i++) {
        res = 0;
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
            res += a[i][j];
        }
        line.push(res);
    }
    for (int j = 1; j <= m; j++) {
        res = 0;
        for (int i = 1; i <= n; i++) {
            res += a[i][j];
        }
        row.push(res);
    }
    for (int i = 1; i <= k; i++) {
        int x = line.top();
        line.pop();
        suml[i] = suml[i - 1] + x;
        x -= m * p;
        line.push(x);
    }
    for (int j = 1; j <= k; j++) {
        int y = row.top();
        row.pop();
        sumr[j] = sumr[j - 1] + y;
        y -= n * p;
        row.push(y);
    }
    for (int z = 0; z <= k; z++) {
        ans = max(ans, suml[z] + sumr[k - z] - p * (k - z) * z);
    }
    cout << ans << '\n';
    return 0;
}

CSP模拟21#

T1:[CEOI2016] kangaroo#

思路我想不到,就是将一个数看成一个块,相当于将 1n 当作一个一个的块去插到当前有的块之间 j1 个块,所以有 j 个空,还要判断 st 是否已经插入,如果插入那么空应该减少,最后合并块即可。合并 st 的时候有点特殊,因为只能放在左边或右边,特判一下即可。具体 dp 转移:

插入新块:f[i][j]=f[i1][j1]j

合并块:f[i][j+1]=f[i1][j+1]j

起(终)点的合并相当于在块中加一个元素:f[i][j]=f[i1][j]

具体看代码即可,我觉得应该可以明白吧。

代码实现:

点击查看代码
#define int long long
using namespace std;
const int mod=1e9+7;
int n,s,t;
int f[2010][2010];
signed main()
{
    cin >> n >> s >> t;
    f[1][1]=1;
    for (int i=2;i<=n;i++)
    {
        for (int j=1;j<=n;j++)
        {
            if (i==s || i==t)
            {
                f[i][j]+=f[i-1][j]+f[i-1][j-1];
                f[i][j]%=mod;
                continue;
            }
            int res=(i>s)+(i>t);
            f[i][j]+=f[i-1][j-1]*(j-res);
            f[i][j]+=f[i-1][j+1]*j;
            f[i][j]%=mod;
        }
    }
    cout << f[n][1] << '\n';
    return 0;
}

T2:[JOI 2023 Final] Advertisement 2#

当我们做题遇到绝对值的时候,应该考虑将绝对值拆开,XiXj∣⩽EiEj ,我们就可以转化为 XiXjEiEj and XjXiEiEj ,简单移项,使相同下标的变量放在同一侧,就可以得出这样的一个式子:

{EjXjEiXiEj+XjEi+Xi

那么,我们就可以化简一下这个式子,设 xi=EiXi ,yi=Ei+Xi ,那么式子就可以变得简单,我们先算出 x ,y ,然后可以先将 x 进行排序,这样我们只需满足 y 的条件即可,这里我们可以用一个单调栈来维护一个递减栈(可以画图理解一下,比较好理解),最后栈里的所有元素就是我们要求的个数。注意排序 x 的时候,要将 y 作为第二关键词排序,否则最后的答案可能会偏大。
代码实现:

点击查看代码
struct node {
    int x, y;
    friend bool operator<(node x1, node x2) {
        if (x1.x == x2.x)
            return x1.y < x2.y;

        return x1.x < x2.x;
    }
}
a[500010];
int n, o, p, top;
int sta[500010];
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> o >> p;
        a[i].x = o + p;
        a[i].y = p - o;
    }
    sort(a + 1, a + 1 + n);
    for (int i = 1; i <= n; i++) {
        while (top && a[sta[top]].y <= a[i].y)
            top--;

        sta[++top] = i;
    }
    cout << top << '\n';
    return 0;
}

T3:Your#

先考虑点的数量,题目说不存在三条及以上条的线过某一点,那么在⚪中四个点会有一个交点,所以交点的总数为 Cn4 ,那么总的点数就是 Cn4+n ;然后考虑边的数量,肯定不能直接套欧拉定理,因为要先满足是平面图,这时就应该利用一种方法:两条相交的线,可以认为是交点是一个新的点,将一条直线分为两半,那么总的边数就相当于增加了 2 ,不要忘了圆弧也是线,所以最终得到的总边数为 Cn2+2Cn4+n ,最后套题目给的式子就可以了,减去最外面的无穷平面就可以得出总的面数了。自己算一算,应该是 Cn4+Cn2+1 。记得开 long long

代码实现:

点击查看代码
#define int long long
using namespace std;
const int mod = 998244353;
int a, b, n, x, y;
int qpow(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1)
            res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
signed main() {
    cin >> n;
    x = qpow(24, mod - 2);
    y = qpow(2, mod - 2);
    a = (n % mod * (n - 1) % mod * (n - 2) % mod * (n - 3) % mod) % mod * x % mod;
    b = (n % mod * (n - 1) % mod) % mod * y % mod;
    cout << a % mod << '\n';
    cout << (a + b + 1) % mod << '\n';
    return 0;
}

CSP模拟22#

T1:The Child and Toy#

要求最后的答案值最小,考虑贪心,我们不难发现,删去一个点,其实就是删去和这个点相连的边,那么我们要想最终答案最小,就必须将点权小的边删去,大的留下,这样才能得出最优方案,所以我们直接在建边的时候删边,将两点之间较小的点权减去,加入答案中,最后输出答案即可。

代码实现:

点击查看代码
#define int long long
using namespace std;
int n, m, a, b, ans;
int val[1000010];
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> val[i];
	for (int i = 1; i <= m; i++) {
		cin >> a >> b;
		ans += min(val[a], val[b]);
	}
	cout << ans << '\n';
	return 0;
}

CSP模拟25#

T1:[ARC128A] Gold and Silver#

考虑用 dp 转移,我们先去算出最后可以得到的最大的钱数,在 dp 的时候记录每一个值都是从哪里转移来的,最后递归输出答案即可。接下来想 dp 转移式,我们用 dp[i][j][k] 表示我们在第 i 天,是否转移 j,和当前的货币是什么 k。如果交换,那么就去看前一天相对货币转移和不转移的较大值,并且记录当前最大值是由谁(指前一天是否转移)转移而来;不交换就看前一天相同货币即可。因为这一道题卡精度,所以直接乘除是不可以的,要现将值去个对数,加减对数即可。

代码实现:

点击查看代码
const int M=2e5+10;

int n,num;

double a[M],b[M];
int ro[M],rt[M],rs[M],rf[M],ans[M];

double f[M][2][2];

void getans(int x,int p)
{
	if (num<=0) return;
	if (x && p)
	{
		ans[num]=x;
		num--;
		getans(ro[num+1],p^1);
	}
	if (x && !p)
	{
		ans[num]=x;
		num--;
		getans(rs[num+1],p^1);
	}
	if (!x && p)
	{
		ans[num]=x;
		num--;
		getans(rt[num+1],p);
	}
	if (!x && !p)
	{
		ans[num]=x;
		num--;
		getans(rf[num+1],p);
	}
}

int main()
{
	cin >> n;
	for (int i=1;i<=n;i++)
	{
		cin >> a[i];a[i]=log(a[i]);
		b[i]=a[i];
	}
	sort(a+1,a+1+n);
	int len=unique(a+1,a+1+n)-a-1;
	for (int i=1;i<=n;i++)
		b[i]=lower_bound(a+1,a+len+1,b[i])-a;
	f[0][0][0]=1;
	for (int i=1;i<=n;i++)
	{
		if (f[i-1][0][0]>f[i-1][1][0]) ro[i]=0;
		else ro[i]=1;
		f[i][1][1]=max(f[i-1][0][0],f[i-1][1][0])+b[i];
		if (f[i-1][0][1]>f[i-1][1][1]) rs[i]=0;
		else rs[i]=1;
		f[i][1][0]=max(f[i-1][0][1],f[i-1][1][1])-b[i];
		if (f[i-1][0][1]>f[i-1][1][1]) rt[i]=0;
		else rt[i]=1;
		f[i][0][1]=max(f[i-1][0][1],f[i-1][1][1]);
		if (f[i-1][0][0]>f[i-1][1][0]) rf[i]=0;
		else rf[i]=1;
		f[i][0][0]=max(f[i-1][0][0],f[i-1][1][0]);
	}
	num=n;
	if (f[n][1][0]>f[n][0][0]) getans(1,0);
	else getans(0,0);
	for (int i=1;i<=n;i++)
		cout << ans[i] << ' ';
	cout << '\n';
	return 0;
}

T2:Make N#

这道题首先应该分类讨论。你把它当做一道普通数学题去思考,你想让你最终的价格尽量小,那你就要让性价比高的放的次数最多,这是我们分别算出 AB1 需要花费的价格,记为 pja pjb,我们考虑三种情况:

第一种:X<pja and X<pjb 那么我们只需用 1 填满所有空就可以了,答案为 nX

第二种:X 是第二小的位置,那么就尽可能多的用第一小的那个数去填,最后用 1 去补空就可以了。

第三种:就是 X 是最大的情况,这时我们就不可以只用简单的贪心去考虑问题,因为这样可能会出现我们原先可以用不到 1,但因为填的第一个数太多,导致第二个数无法填满,不得不填 1。这是考虑根号分治,分别枚举除一以外的两个数的数量,然后依次去补位,得出最终的答案。

代码实现:

点击查看代码
int read()
{
	int f(1),x(0);char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
	for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
	return f*x;
}

void write(int x)
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

int n,a,b,x,y,z,ans,T;

signed main()
{
	T=read();
	while(T--)
	{
		ans=0;
		n=read();a=read();b=read();x=read();y=read();z=read();
		long double pja=y/a,pjb=z/b;
		if (pja>pjb)
			swap(pja,pjb),swap(a,b),swap(y,z);
		if(x<=pja && x<=pjb)
		{
			ans=n*x;
		}
		else if(x<pjb && x>pja)
		{
			ans+=y*(n/a);
			ans+=x*(n%a);
		}
		else
		{
			ans=1e18;
			for (int i=0;i<=ceil(sqrt(n));i++)
			{
				int res=0,temp;
				res+=y*i;temp=n-a*i;
				if(temp<0) continue;
				res+=z*(temp/b);
				res+=x*(temp%b);
				ans=min(ans,res);
			}
			for (int i=0;i<=sqrt(n);i++)
			{
				int res=0,temp;
				res+=z*i;
				temp=n-b*i;
				if(temp<0) continue;
				res+=y*(temp/a);
				res+=x*(temp%a);
				ans=min(ans,res);
			}
		}
		write(ans);putchar('\n');
	}
	return 0;
}

CSP模拟26#

T1:[AGC031B] Reversi#

考场上一直在想这道题是不是应该找到什么通用规律去解,后来发现好像想不出来,然后就骗了 10 分,正解应该是线性 dp,只需要一维 dp 即可,我们用 f[i] 表示枚举到第 i 个石头时的方案数,那么这时应该分类讨论,如果这块石头和它前一块石头颜色相同,那么这两块石头的贡献方案数就是相同的,因为靠前的石头方案数已经更新过,所以只需要传递一下即可:

if(c[i]==c[i-1]) f[i]=f[i-1];

如果不是这种情况,我们就要将当前石头和之前所有相同颜色的石头之间染上色,思考一下,我们可以直接加上据他最近的石头的位置的方案数,因为我们之前也是这样推过来的,所以这样就可以涵盖所有的新方案数,再传递一下 f[i-1] 即可,这一步具体可以看代码实现,应该挺好理解的……

代码实现:

点击查看代码
const int mod=1e9+7;

int n;

int c[5000010],now[5000010],pre[5000010],f[5000010];

int main()
{
	// file("a.in");
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		cin >> c[i];
		pre[i]=now[c[i]];
		now[c[i]]=i;
	}
	f[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(c[i]==c[i-1]) f[i]=f[i-1];
		else f[i]=(f[pre[i]]+f[i-1])%mod;
	}
	cout << f[n]%mod << '\n';
	return 0;
}

作者:Aewrxuk

出处:https://www.cnblogs.com/Aewrxuk/p/17644036.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Aewrxuk  阅读(42)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示