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
打得太烂了!加训!

浙公网安备 33010602011771号