2021 CCPC “科大讯飞杯”上海市赛题解 更新至 11 题

Preface

好久没有写题解了,前一阵子直接开摆了,游山玩水+搞项目,就是没搞ICPC。

现在马上就要邀请赛了,这次训练VP打得很烂,所以直接补题补完了(除了大模拟)。

这次的费用流让我体会很深,之前学的那些上下界网络流几乎全忘光了,这次拾起来了。

另外这次的DP真的感觉不会做啊,比之前的DP感觉难了不少了,只能说没想到。

H题这次也是漏了个情况,写了个粑粑出来。哎,加油奥利给吧。

我会在代码一些有必要的地方加上注释,签到题可能一般就不会写了.

以下是代码火车头:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#include <queue>
#include <map>
#include <unordered_map>
#include <iomanip>
#define endl '\n'
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define rep2(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;

template<typename T>
void cc(const vector<T> &tem) {
    for (const auto &x: tem) cout << x << ' ';
    cout << endl;
}

template<typename T>
void cc(const T &a) { cout << a << endl; }

template<typename T1, typename T2>
void cc(const T1 &a, const T2 &b) { cout << a << ' ' << b << endl; }

template<typename T1, typename T2, typename T3>
void cc(const T1 &a, const T2 &b, const T3 &c) { cout << a << ' ' << b << ' ' << c << endl; }

void cc(const string &s) { cout << s << endl; }

void fileRead() {
#ifdef LOCALL
    freopen("D:\\AADVISE\\Clioncode\\untitled2\\in.txt", "r", stdin);
    freopen("D:\\AADVISE\\Clioncode\\untitled2\\out.txt", "w", stdout);
#endif
}

void kuaidu() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); }

inline int max(int a, int b) {
    if (a < b) return b;
    return a;
}

inline double max(double a, double b) {
    if (a < b) return b;
    return a;
}

inline int min(int a, int b) {
    if (a < b) return a;
    return b;
}

inline double min(double a, double b) {
    if (a < b) return a;
    return b;
}

void cmax(int &a, const int &b) { if (b > a) a = b; }
void cmin(int &a, const int &b) { if (b < a) a = b; }
void cmin(double &a, const double &b) { if (b < a) a = b; }
void cmax(double &a, const double &b) { if (b > a) a = b; }
using PII = pair<int, int>;
using i128 = __int128;
using vec_int = std::vector<int>;
using vec_char = std::vector<char>;
using vec_double = std::vector<double>;
using vec_int2 = std::vector<std::vector<int> >;
using que_int = std::queue<int>;

Problem A. 小 A 的点面论

队友写的,我不晓得呢。

#include<bits/stdc++.h>
#define endl '\n'

signed main(void){
    int x,y,z;
    int a,b,c;
    std::cin>>x>>y>>z;
    std::cin>>a>>b>>c;
    std::cout<<b*z-y*c<<' '<<x*c-a*z<<' '<<a*y-x*b<<endl;
    return 0;
}

Problem B. 小 A 的卡牌游戏

一个有点典型的DP其实,这个难度我觉得比D简单不少呢,为什么D过的这么多,榜歪了吗?

But我首先就是搓了一个网络流上去,不管时间复杂度,只能说狠狠地吃了一发TLE。

然后常规想法:如果是两个的话,其实我们就可以直接经典排序a_i-b_i>a_j-b_j。然后优先选a,再剩下的选b。

于是推广到3个的基础上,多了个c出来的话,貌似是可以直接来个背包的。

设状态dp[i][j]是前i个物品里买了j个c,那么状态式子可以有dp[i-1][j-1]+c_i,dp[i-1][j]+a_i or b_i(通过当前的i来判断)转移过来。

然后就没了,真的觉得比D简单呢!

//--------------------------------------------------------------------------------
const int N = 5e3 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;

//--------------------------------------------------------------------------------
//global variable:
int dp[N][N];

//struct or namespace:
struct node {
	int a;
	int b;
	int c;
};

node A[N];
//--------------------------------------------------------------------------------

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	//cin >> T;
	while (T--) {
		int a, b, c;
		cin >> n >> a >> b >> c;
		rep(i, 1, n) {
			int q, w, e;
			cin >> q >> w >> e;
			A[i] = {q, w, e};
		}
		sort(A + 1, A + n + 1, [](node q1, node q2) {
			return q1.a - q1.b > q2.a - q2.b;
		});

		rep(i, 0, N-1)
			rep(j, 0, N-1) dp[i][j] = -INF;
		dp[0][0] = 0;

		rep(i, 1, n) {
			rep(j, 0, n) {
				if (j >= 1) cmax(dp[i][j], dp[i - 1][j - 1] + A[i].c);
				int las = i - j;
				if (las <= a) cmax(dp[i][j], dp[i - 1][j] + A[i].a);
				else cmax(dp[i][j], dp[i - 1][j] + A[i].b);
			}
		}
		cc(dp[n][c]);

	}
	return 0;
}

/*


*/

Problem C. 小 A 的期末考试

队友写的,我不晓得呢。

#include<bits/stdc++.h>
#define endl '\n'

int n,m;
int arr[105];
double avr;

signed main(void){
    std::cin>>n>>m;
    for(int i=1;i<=n;++i){
        int s,a; std::cin>>s>>a;
        arr[s]=a;
    }
    for(int i=1;i<=n;++i){
        avr+=arr[i];
    }
    avr/=n;
    for(int i=1;i<=n;++i){
        if(i==m){
            arr[i]=std::max(arr[i],60);
            continue;
        }
        if(arr[i]>=avr){
            arr[i]=std::max(0,arr[i]-2);
        }
    }
    for(int i=1;i<=n;++i)std::cout<<arr[i]<<' ';
    return 0;
}

Problem D. Zztrans 的班级合照

勾八题,一点做不来。

dp式子的定义是:dp[i][j]:已经排了原数列的i个人,此时第一排的数量比第二排的数量多j个人。

然后我们要从小到大枚举身高,首先记录下来相同身高的人的个数,枚举到当前身高的时候是可以随便排的。

然后假设当前身高的人的个数是c,那么我们就枚举k,代表下排这次我们打算放k个人,上排就是c-k个人,那么多出来的人就是k-(c-k)。

我们for循环的第二层是枚举j,第三层是枚举k。

那么我们可以写出来式子:

\(dp[i+c][j+k-(c-k)]+=dp[i][j]*C(c,k)*k!*(c-k)!\)

其中C(c,k)就是从c个人里选出来k个人的组合,然后乘上k的阶乘和(c-k)的阶乘,代表各自的排列。

然后我们其实可以注意到第一维度我们也是可以直接滚动优化的,所以直接去掉第一维度就好了。

//--------------------------------------------------------------------------------
//global variable:
int A[N], cnt[N];
int dp[2][N];
int dp1[N], dp2[N];

//struct or namespace:
namespace ms {

	namespace ni {
		vector<int> fact, infact;

		void cal_ni() {
			fact.resize(N + 5);
			infact.resize(N + 5);
			fact[0] = infact[0] = 1;
			for (int i = 1; i < N; i++) fact[i] = fact[i - 1] * i % mod;
			infact[N - 1] = Kuai<mod>(fact[N - 1], mod - 2);
			for (int i = N - 2; i >= 1; i--) infact[i] = infact[i + 1] * (i + 1) % mod;
		}
	}

	namespace pri {
		vector<int> pri;
		bool ispri[N], biao[N];

		void cal_pri() {
			for (int i = 2; i < N; i++) {
				if (biao[i]) continue;
				pri.push_back(i);
				for (int j = i; j < N; j += i) biao[j] = 1;
			}
			for (auto &x: pri) ispri[x] = 1;
		}
	}

}

//--------------------------------------------------------------------------------

int C(int n,int m) {

	// cc(n, m);
	if (n < 0 or n < m or m < 0) return 0;
	using namespace ms::ni;
	return fact[n] * infact[n - m] % mod * infact[m] % mod;
}


signed main() {
	fileRead();
	kuaidu();
	T = 1;
	//cin >> T;

	ms::ni::cal_ni();

	while (T--) {
		cin >> n;
		rep(i, 1, n) {
			cin >> A[i];
			cnt[A[i]]++;
		}

		int x = 1, y = 0;

		dp[x][0] = 1;

		vec<int> tem;
		rep(i, 1, n) {
			if (cnt[i]) tem.push_back(cnt[i]);
		}
		for (auto &c: tem) {
			rep(i, 0, n) dp[y][i] = dp[x][i];
			memset(dp[x], 0, sizeof(dp[x]));

			rep(i, 0, n) {
				rep(j, 0, c) {
					int more = j - (c - j); // shang: c-j   xia:j
					using namespace ms::ni;
					if (i + more >= N or i + more < 0) continue;
					dp[x][i + more] += dp[y][i] * fact[j] % mod * fact[c - j] % mod * C(c, j) % mod;
					dp[x][i + more] %= mod;
				}

			}


		}
		cout << dp[x][0] << endl;
	}
	return 0;
}

/*


*/

Problem E. Zztrans 的庄园

直接字面模拟就好了,只不过题面看了一会。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;

int dfs[N];
//--------------------------------------------------------------------------------
//struct or namespace:

//--------------------------------------------------------------------------------

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    dfs['D'] = 16;
    dfs['C'] = 24, dfs['B'] = 54, dfs['A'] = 80, dfs['S'] = 10000;
    while (T--) {
        cin >> n >> m;
        double ans = 0;
        rep(i, 1, n) {
            char a;
            double b;
            cin >> a >> b;
            ans += (b * dfs[a]);
        }
        ans = (ans - 23) * m;
        // ans -= 23 * m;
        cout << fixed << setprecision(7) << ans << endl;

    }
    return 0;
}

Problem F. 鸡哥的限币令

非常有意思的一个题,虽然了解到了最小费用流的上下界可行流之后就不那么的有意思了,网络流题果然还是套路更多一些。

就是因为这个题,卡了我快一天的时间才搞定这个知识点(无源汇网络流的可行流,有源汇网络流最大流,无源汇费用流上下界可行流,一开始了解非常的恶心)。

之后应该会专门出一期来讲讲这个上下界网络流的问题。

在了解了以上我们需要知道的前置知识之后,这个题的建图就比较的简单,(比什么志愿者招募好理解的多)

首先建一个小源点s,小汇点t。我们根据题意的了解,每一个点我们都知道,出度至少是1,入度也至少是1,所以就是建图的时候,给他们的边的容量下界就是1。上界就是无穷了。所以我们把他们的出度和入度分别看做是两个点(也就是拆点成两个)

然后把每一条边就代表着给第x个点的出度+1,第y个点的入度+1,也就是第x个点的出度(的那个点)向第y个点的入度(的那个点)连边,上下界是无穷和0,费用是边的花费。

然后s向每一个点的出度连边,每一个点的入度向t连边,最后为了保证整个图是流量守恒的,t向s连边,下界是0,花费0,这样我们就构成了一个流量守恒的一个图。

如果没有可行流,那么就是-1.否则就找到了我们的解。

然后输出认可的边,我们只需要遍历一下那些中间连接x和y的边,谁的流量大于0,就代表这个边被认可了。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e9;
auto kuai = Kuai<mod>;
int n, m, T;

//--------------------------------------------------------------------------------
//global variable:

//struct or namespace:
namespace z {

	const int N = 2e5 + 10;
	const int M = 5e5 + 10;

	struct Z {
		int Next;
		int to;
		int val;
		int hf;
	} D[M << 1];

	int cnt, n, m, st, ed;
	int head[N], head1[N], dis[N];
	bool biao[N], vis[N];
	int ans_hf;

	bool SPFA() {
		queue<int> F;
		F.push(st);
		rep(i, 0, n) biao[i] = 0, dis[i] = INF;
		biao[st] = 1, dis[st] = 0;
		while (!F.empty()) {
			int x = F.front();
			F.pop();
			biao[x] = 0;
			for (int i = head[x]; i != -1; i = D[i].Next) {
				int y = D[i].to;
				if (D[i].val and dis[y] > dis[x] + D[i].hf) {
					dis[y] = dis[x] + D[i].hf;
					if (!biao[y]) F.push(y), biao[y] = 1;
				}
			}
		}
		return dis[ed] < INF;
	}

	int dinic(int x, int cost) {
		if (x == ed || cost == 0) return cost;
		int flow = 0;
		vis[x] = 1;
		for (int i = head1[x]; i != -1; i = D[i].Next) {
			auto [qwe, y, val, hf] = D[i];
			head1[x] = i;
			if (val <= 0 or vis[y]) continue;
			if (dis[y] != dis[x] + hf) continue;
			int tem = dinic(y, min(cost - flow, val));
			if (!tem) continue;
			D[i].val -= tem;
			D[i ^ 1].val += tem;
			flow += tem;
			ans_hf += hf * tem;
			if (flow == tem) {
				vis[x] = 0;
				return tem;
			}
		}
		vis[x] = 0;
		if (!flow) vis[x] = 1;
		return flow;
	}

	void clear(int n1, int m1) {
		n = n1 + 7, m = m1 + 7;
		st = 0, ed = n1 + 5;
		cnt = 0;
		ans_hf = 0;
		rep(i, 0, n) head[i] = -1;
	}

	void add(int x, int y, int c, int cost) {
		D[cnt] = {head[x], y, c, cost};
		head[x] = cnt++;
		D[cnt] = {head[y], x, 0, -cost};
		head[y] = cnt++;
	}

	PII work() {
		int ans = 0;
		ans_hf = 0;
		int flow;
		while (SPFA()) {
			rep(i, 0, n) vis[i] = 0, head1[i] = head[i];
			while (flow = dinic(st, INF)) ans += flow;
		}
		return {ans, ans_hf};
	}
}


namespace zlim {

	int in[z::N];
	int base[z::N];
	int sum = 0;
	int fl = -1;

	int base_cost = 0, extra_cost = 0;

	void clear(int n1,int m1) {
		z::clear(n1, m1 + n1 + n1);
		rep(i, 0, z::n) in[i] = 0;
		sum = 0, base_cost = 0, extra_cost = 0;
	}

	void add(int x,int y,int c,int d,int cost) {
		base[z::cnt] = c;
		z::add(x, y, d - c, cost);
		in[y] += c, in[x] -= c;
		base_cost += cost * c;
	}

	void update_flow_edge() {
		fl = z::cnt - 2;
	}

	bool work() {
		sum = 0;
		rep(i, 0, z::n) {
			if (in[i] < 0) z::add(i, z::ed, -in[i], 0);
			if (in[i] > 0) z::add(z::st, i, in[i], 0), sum += in[i];
		}
		auto res = z::work();
		extra_cost = res.second;
		return (res.first == sum);
	}
	int cost() { return base_cost + extra_cost; }
	int flow() { return z::D[fl ^ 1].val; }

}

//--------------------------------------------------------------------------------
struct node {
	int y;
	int val;
	int id;
};

struct ed {
	int x;
	int y;
	int val;
};

vec<node> A[N];
vec<ed> e;

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	//	cin>>T;
	while (T--) {
		cin >> n >> m;

		zlim::clear(n + n + 2, n + n + m);
		int s = n + n + 1, t = n + n + 2;

		rep(i, 1, n) {
			zlim::add(s, i, 1, INF, 0);
			zlim::add(n + i, t, 1, INF, 0);
		}

		rep(i, 1, m) {
			int a, b, c;
			cin >> a >> b >> c;
			zlim::add(a, b + n, 0, INF, c);
		}
		zlim::add(t, s, 0, INF, 0);
		zlim::update_flow_edge();

		if (zlim::work()) {
			cc(zlim::cost());
			vec<int> ans;

			for (int i = 4 * n; i < zlim::fl; i += 2) {
				if (z::D[i ^ 1].val > 0) {
					ans.push_back(i / 2 - n - n + 1);
				}
			}
			cout << ans.size() << " ";
			for (auto &x: ans) cout << x << " ";
			// cc(ans);
			// rep(i, (n+n)*2, z::cnt-1) {
			//
			//
			// }
		}
		else {
			cc(-1);
		}


	}

	return 0;
}

/*

41
5 1 3 4 7 9

*/

Problem G. 鸡哥的雕像

一个有点小坑的题,因为a_i的范围是在1e9,所以有可能mod上的这个998244353可能是0,所以我们不用整体的乘积除以当前的数的逆元,可以用前缀积和后缀积来计算,当然也会有别的计算方法。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 998244353;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:

//--------------------------------------------------------------------------------
int A[N];
int pre[N], suf[N];

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    auto kuai = Kuai<mod>;
    while (T--) {
        cin >> n;
        int sum = 1;
        rep(i, 1, n) {
            cin >> A[i];
            A[i] %= mod;
        }
        pre[0] = 1;
        rep(i, 1, n) pre[i] = pre[i - 1] * A[i] % mod;
        suf[n + 1] = 1;
        rep2(i, n, 1) suf[i] = suf[i + 1] * A[i] % mod;
        rep(i, 1, n) {
            cout << pre[i - 1] * suf[i + 1] % mod << " ";
        }

    }
    return 0;
}

Problem H. 鸡哥的 AI 驾驶

我们首先可以注意到,如果车与车的相对位置发生了变化,那么就说明不行。

一开始写的做法是,先按照pos去排序,然后只看型号,会得到一个数组,例如:1,1,1,2,3,2,3,1

这个数组是型号的,那么二分答案之后如果对应的型号的数组发生了变化,就说明不行。

但是这个做法是假的,因为如果原本的是1,2,1,然后移动之后还是1,2,1,也有可能是两边的1向对方穿了过去,答案还是1,2,1

所以我们还有一个修改的地方就是:我们可以记录下来对于一个编号的车,我们向左最左可以到达哪个编号,向右也是这样。

例如1,2,1,1。这个数据,那么右边的1,可以走到的下标的范围就是[3,4],如果超出了这个区间,也代表他与别的车的相对位置是发生了变化的。

所以就二分,然后处理就好了。当然,这个做法是nlogn*logn的。也有logn的做法,这里不细究。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:
struct node {
	int pos;
	int v;
	int id;
	int idx;
};

node A[N];
node B[N];
int posl[N], posr[N];
//--------------------------------------------------------------------------------

bool check(int mid) {

	rep(i, 1, n) {
		B[i] = A[i];
		B[i].pos += B[i].v * mid;
	}
	sort(B + 1, B + n + 1, [&](node q1, node q2) {
		if (q1.pos == q2.pos) return q1.id < q2.id;
		return q1.pos < q2.pos;
	});

	rep(i, 1, n) {
		if (i != 1 and B[i - 1].pos == B[i].pos and B[i].id != B[i - 1].id) return false;
		if (i < posl[B[i].idx] or posr[B[i].idx] < i) return false;
	}

	return true;
}

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	//cin >> T;
	while (T--) {
		cin >> n >> m;
		rep(i, 1, n) {
			int a, b, c;
			cin >> a >> b >> c;
			A[i] = {a, b, c, i};
		}
		sort(A + 1, A + n + 1, [&](node q1, node q2) {
			if (q1.pos == q2.pos) return q1.id < q2.id;
			return q1.pos < q2.pos;
		});

		rep(i, 1, n) {
			if (A[i].id == A[i - 1].id) posl[A[i].idx] = posl[A[i - 1].idx];
			else posl[A[i].idx] = i;
		}
		rep2(i, n, 1) {
			if (A[i].id == A[i + 1].id) posr[A[i].idx] = posr[A[i + 1].idx];
			else posr[A[i].idx] = i;
		}
		int l = 0, r = 4e9 + 5;
		while (l + 1 != r) {
			int mid = l + r >> 1;
			if (check(mid)) l = mid;
			else r = mid;
		}
		if (l == 4e9 + 4) l = -1;
		cc(l);

	}
	return 0;
}


Problem I. 对线

一个比较典的线段树题,最近做过一两道这种维护矩阵的题,所以还好。

我们可以开一个线段树,节点维护的信息是一个4*1的矩阵,前三个点是代表第i排的sum,第四个是一个恒定的1,方便我们做区间加等操作。

然后如果我们要给x排的l,r区间加上y,就是乘上一个4*4的单位矩阵,然后在(x,4)的位置设成y。所以区间加的操作,就被等价成了乘上一个矩阵的操作。

然后交换位置,就是乘上一个单位矩阵第x行和第y行交换的矩阵。

分身术这个操作也是这样的同理,这里不细说了。

主要是代码有些恶心。

//--------------------------------------------------------------------------------
const int N = 3e5 + 10;
const int M = 1e6 + 10;
const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;

//--------------------------------------------------------------------------------
//global variable:

//struct or namespace:
struct Mat {
	// vec<vec<int> > a;
	int a[5][5] = {};
	int n, m, st;

	Mat(int n_, int m_, int fl = 0, int st_ = 1) {
		n = n_, m = m_, st = st_;
		// a.resize(n_ + 1);
		// rep(i, st_, n_) a[i].resize(m_ + 1);
		clear(fl);
	}

	int *operator [](int x) { return a[x]; }

	Mat operator *(Mat b) {
		Mat res(n, b.m, 0, b.st); //记得赋值
		rep(i, st, n)
			rep(j, st, b.m)
				rep(k, st, m) {
					res[i][j] += a[i][k] * b[k][j] % mod;
					res[i][j] %= mod;
				}
		return res;
	}

	Mat operator +(Mat b) {
		Mat res(n, b.m, 0, b.st); //记得赋值
		rep(i, st, n)
			rep(j, st, b.m) {
				res[i][j] = a[i][j] + b[i][j];
				res[i][j] %= mod;
			}
		return res;
	}

	static Mat kuai(Mat A, int b) {
		Mat tem(A.n, A.m, 1, A.st);
		while (b) {
			if (b % 2) tem = tem * A;
			b = b / 2, A = A * A;
		}
		return tem;
	}

	//指数从0开始的求和
	static Mat kuai_sum(Mat A, int b) {
		Mat tem(A.n, A.m, 1, A.st);
		Mat sum = tem;
		while (b) {
			if (b % 2) tem = sum + tem * A;
			sum = sum + sum * A;
			A = A * A, b /= 2;
		}
		return tem;
	}

	void clear(int fl = 1) {
		if (!fl || (fl == 2)) {

			rep(i, st, n)
				rep(j, st, m) a[i][j] = 0;
			if ((fl == 2))a[n][m] = 1;
		}
		if (fl == INF)
			rep(i, st, n)
				rep(j, st, m) a[i][j] = INF;
		if (fl == 1)
			rep(i, st, n)
				rep(j, st, m) a[i][j] = (i == j);

	}

	void out() {
		rep(i, st, n) {
			rep(j, st, m) cout << a[i][j] << " ";
			cout << endl;
		}
	}
};

class SEG {
#define xl x+x
#define xr x+x+1

	//TODO 节点维护信息、apply函数、up函数
	struct info {
		Mat sum{4, 1, 2};
		int siz = 1;
		Mat lan{4, 4, 1};

		void apply(Mat k) {
			lan = k * lan;
			rep(i, 1, 3)
				rep(j, 4, 4) {
					if (i != j and k[i][j]) k[i][j] *= siz, k[i][j] %= mod;
				}
			sum = k * sum;
		}

		void modify(int k) {
			// mmin = k;
			// siz = 1;
			// lan = 0;
		}

		friend info operator+(const info &q1, const info &q2) {
			info q;
			q.siz = q1.siz + q2.siz;
			auto m1 = q1.sum, m2 = q2.sum;
			q.sum = m1 + m2;
			q.sum[4][1] = 1;
			return q;
		}
	};

	int L, R;
	info F[unsigned(N * 4)];

	void init(int x, int l, int r) {
		if (l == r) {
			F[x] = info();
			return;
		}
		int mid = l + r >> 1;
		init(xl, l, mid), init(xr, mid + 1, r);
		F[x] = F[xl] + F[xr];
	}

	void down(int x) {
		// if (F[x].lan) return;
		F[xl].apply(F[x].lan), F[xr].apply(F[x].lan);
		F[x].lan.clear();
	}

	void add(int x, int l, int r, int l1, int r1, const Mat &k) {
		if (l1 > r1) return;
		if (l != r) down(x);
		// F[x].lan.clear();
		if (l1 <= l and r <= r1) {
			F[x].apply(k);
			return;
		}
		int mid = l + r >> 1;
		if (r1 <= mid) add(xl, l, mid, l1, r1, k);
		else if (mid < l1) add(xr, mid + 1, r, l1, r1, k);
		else add(xl, l, mid, l1, mid, k), add(xr, mid + 1, r, mid + 1, r1, k);
		F[x] = F[xl] + F[xr];
	}

	void modi(int x, int l, int r, int pos, int k) {
		if (pos < l or pos > r) return;
		if (l != r) down(x);
		if (pos == l and pos == r) {
			F[x].modify(k);
			return;
		}
		int mid = l + r >> 1;
		if (pos <= mid) modi(xl, l, mid, pos, k);
		else modi(xr, mid + 1, r, pos, k);
		F[x] = F[xl] + F[xr];
	}

	info qry(int x, int l, int r, int l1, int r1) {
		if (l1 > r1) return info();
		if (l != r) down(x);
		if (l1 <= l and r <= r1) return F[x];
		int mid = l + r >> 1;
		if (r1 <= mid) return qry(xl, l, mid, l1, r1);
		else if (mid < l1) return qry(xr, mid + 1, r, l1, r1);
		else { return qry(xl, l, mid, l1, mid) + qry(xr, mid + 1, r, mid + 1, r1); }
	}
#undef xl
#undef xr

public:
	void clear(int l, int r) {
		L = l, R = r;
		init(1, l, r);
	}

	void add(int l, int r, const Mat &k) {
		cmax(l, L);
		cmin(r, R);
		add(1, L, R, l, r, k);
	}

	void modi(int pos, int k) { modi(1, L, R, pos, k); }

	info qry(int l, int r) {
		cmax(l, L);
		cmin(r, R);
		return qry(1, L, R, l, r);
	}
};

SEG seg;
//--------------------------------------------------------------------------------

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	//cin >> T;
	while (T--) {
		int q;
		cin >> n >> q;
		seg.clear(1, n);
		rep(i, 1, q) {
			int op, x, y, l, r;
			cin >> op;
			if (op == 0) {
				cin >> x >> l >> r;
				auto ans = seg.qry(l, r);
				// cc("ANS");
				// ans.sum.out();
				// cc("");
				cc(ans.sum[x][1] % mod);
			}
			else if (op == 1) {
				cin >> x >> l >> r >> y;
				Mat mat{4, 4, 1};
				mat[x][4] = y;
				// mat.out();
				seg.add(l, r, mat);
			}
			else if (op == 2) {
				cin >> x >> y >> l >> r;
				Mat mat{4, 4, 1};
				mat[x][x] = 0, mat[y][y] = 0;
				mat[x][y] = 1, mat[y][x] = 1;
				seg.add(l, r, mat);
			}
			else {
				cin >> x >> y >> l >> r;
				Mat mat{4, 4, 1};
				mat[y][x] += 1;
				seg.add(l, r, mat);
			}
		}


	}
	return 0;
}

/*


*/

Problem J.Alice and Bob I

直接瞎蒙sort排序,从小到大和从大到小走一遍。

#include<bits/stdc++.h>
#define ll long long
#define endl '\n'

int n;
ll ans;
ll ansa,ansb;
ll arr[5005];

signed main(void){
    std::ios::sync_with_stdio(false),std::cin.tie(0);
    std::cin>>n;
    for(int i=1;i<=n;++i){
        std::cin>>arr[i];
    }
    std::sort(arr+1,arr+n+1);
    ans=-1e18;
    ansa=ansb=0;
    for(int i=1;i<=n;i+=2){
        ansa+=arr[i];
        ansb+=arr[i+1];
    }
    ans=std::max((ll)ans,llabs(ansa)-llabs(ansb));
    ansa=ansb=0;
    for(int i=n;i>=1;i-=2){
        ansa+=arr[i];
        ansb+=arr[i-1];
    }
    ans=std::max((ll)ans,llabs(ansa)-llabs(ansb));
    std::cout<<ans<<endl;
    return 0;
}

Problem K. Alice and Bob II

首先想到,主要是字符串中不同的字符会有关联,如果是abbc和effv这两个,其实没啥太大区别。

然后结合记忆化搜索这样的剪枝操作,利用哈希。然后再加上需要有一个SG函数的前置知识,把每一个字符串做一个异或和,如果是0就代表输,非零代表赢。

具体看代码就好了,这个代码比较的简洁好懂一些。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;

//--------------------------------------------------------------------------------
//global variable:

//struct or namespace:
namespace hs {
	unordered_map<int,int> mp;
	const int base = 131;
	const int mod = 998244353;


	bool find(int hash) {
		if (mp.count(hash) > 0) return true;
		return false;
	}

	void insert(int key,int val) {
		mp.insert({key, val});
	}

	void clear() {
		mp.clear();
	}

	int getHash(const vec<int> &A) {
		int res = 0;
		for (auto &x: A) {
			if (x == 0) continue;
			res = res * base % mod + x;
			res %= mod;
		}
		return res;
	}
}

int getMex(vec<int> S) {
	sort(S.begin(), S.end());
	int res = 0;
	for (auto &x: S) {
		if (x < res) continue;
		if (x == res) res++;
		else break;
	}
	return res;
}

//--------------------------------------------------------------------------------

int dfs(vec<int> tem) {

	sort(tem.begin(), tem.end());
	int ha = hs::getHash(tem);
	if (hs::find(ha) == true) {
		// cc("ASd");
		return hs::mp[ha];
	}

	vec<int> S;

	rep(i, 0, tem.size()-1) {
		if (tem[i] == 0) continue;
		tem[i] -= 1;
		S.push_back(dfs(tem));
		tem[i] += 1;
	}

	rep(i, 0, tem.size()-1) {
		rep(j, i+1, tem.size()-1) {
			if (tem[i] == 0 or tem[j] == 0) continue;
			tem[i] -= 1, tem[j] -= 1;
			S.push_back(dfs(tem));
			tem[i] += 1, tem[j] += 1;
		}
	}

	hs::insert(ha, getMex(S));
	return hs::mp[ha];

}

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	cin >> T;
	while (T--) {
		hs::clear();
		cin >> n;
		int ans = 0;
		rep(i, 1, n) {
			string s;
			cin >> s;
			vec<int> tem, cnt;
			cnt.assign(30, 0);
			for (auto &x: s) cnt[x - 'a']++;
			rep(j, 0, 25) {
				if (cnt[j]) tem.push_back(cnt[j]);
			}
			// cc(tem);
			ans ^= dfs(tem);
		}
		if (ans) cc("Alice");
		else cc("Bob");

	}
	return 0;
}

/*


*/

PostScript

打得太烂了!加训!

posted @ 2025-04-12 19:24  AdviseDY  阅读(84)  评论(0)    收藏  举报