51nod 2023省选联训第一场
A. 如何愉快地与方格玩耍
什么垃圾题目背景
线段树维护行列某区间有多少个位置操作过奇数次
预处理质数个数的前缀和,线段树维护某区间质数多少位置操作过奇数次
记录行列质数合数位置被操作是否为奇数次
统计答案时通过一些简单而细节的容斥算出多少操作过奇数次的行和列
答案就是奇数次的行乘上偶数次的列 + 偶数次的列乘上奇数次的行
code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#define int long long
using namespace std;
const int maxn = 1000005;
typedef long long ll;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c >= '0' && c <= '9');
return x;
}
int prime[maxn],cnt;
int cp[maxn], ch[maxn];
bool flag[maxn];
struct note{
int size, zs;
note(){size = zs = 0;}
note(int x, int y){size = x; zs = y;}
friend note operator +(const note & x, const note & y){
note ans;
ans.size = x.size + y.size;
ans.zs = x.zs + y.zs;
return ans;
}
};
struct tree{
struct node{
int size, tag, zs;
}t[maxn << 2 | 1];
void push_down(int x, int l, int r){
int mid = (l + r) >> 1, ls = x << 1, rs = x << 1 | 1;
t[ls].size = mid - l + 1 - t[ls].size;
t[ls].zs = cp[mid] - cp[l - 1] - t[ls].zs;
t[ls].tag = 1 - t[ls].tag;
t[rs].size = r - mid - t[rs].size;
t[rs].zs = cp[r] - cp[mid] - t[rs].zs;
t[rs].tag = 1 - t[rs].tag;
t[x].tag = 0;
}
void push_up(int x){
t[x].size = t[x << 1].size + t[x << 1 | 1].size;
t[x].zs = t[x << 1].zs + t[x << 1 | 1].zs;
}
void modify(int x, int l, int r, int L,int R){
if(L <= l && r <= R){
t[x].size = r - l + 1 - t[x].size;
t[x].zs = cp[r] - cp[l - 1] - t[x].zs;
t[x].tag = 1 - t[x].tag;
return;
}
if(t[x].tag)push_down(x, l, r);
int mid = (l + r) >> 1;
if(L <= mid)modify(x << 1, l, mid, L, R);
if(R > mid)modify(x << 1 | 1, mid + 1, r, L, R);
push_up(x);
}
note query(int x, int l, int r,int L, int R){
if(L <= l && r <= R)return note(t[x].size, t[x].zs);
if(t[x].tag)push_down(x, l, r);
int mid = (l + r) >> 1;
note ans;
if(L <= mid)ans = ans + query(x << 1, l, mid, L, R);
if(R > mid)ans = ans + query(x << 1 | 1, mid + 1, r, L, R);
return ans;
}
}H, L;
char c[3];
signed main(){
int n = read();
for(int i = 2; i <= n; ++i){
if(!flag[i])prime[++cnt] = i;
for(int j = 1; j <= cnt && i * prime[j] <= n; ++j){
flag[i * prime[j]] = 1;
if(i % prime[j] == 0)continue;
}
}
for(int i = 2; i <= n; ++i)cp[i] = cp[i - 1] + 1 - flag[i];
for(int i = 2; i <= n; ++i)ch[i] = ch[i - 1] + flag[i];
int fzsh = 0, fzsl = 0, fhsh = 0, fhsl = 0;
int q = read();
for(int i = 1; i <= q; ++i){
scanf("%s",c);
if(c[0] == 'M'){int l = read(), r = read();H.modify(1, 1, n, l, r);}
if(c[0] == 'N'){int l = read(), r = read();L.modify(1, 1, n, l, r);}
if(c[0] == 'P'){if(read())fzsl = 1 - fzsl;else fzsh = 1 - fzsh;}
if(c[0] == 'R'){if(read())fhsl = 1 - fhsl;else fhsh = 1 - fhsh;}
if(c[0] == 'Q'){
int l1 = read(), r1 = read(), l2 = read(), r2 = read();
note h = H.query(1, 1, n, l1, r1);
if(fzsh){
h.size = h.size - h.zs + cp[r1] - cp[l1 - 1] - h.zs;
h.zs = cp[r1] - cp[l1 - 1] - h.zs;
}
if(fhsh){
h.size = ch[r1] - ch[l1 - 1] - h.size + h.zs + h.zs;
if(l1 == 1 && H.query(1, 1, n, 1, 1).size)h.size += 2;
}
note l = L.query(1, 1, n, l2, r2);
if(fzsl){
l.size = l.size - l.zs + cp[r2] - cp[l2 - 1] - l.zs;
l.zs = cp[r2] - cp[l2 - 1] - l.zs;
}
if(fhsl){
l.size = ch[r2] - ch[l2 - 1] - l.size + l.zs + l.zs;
if(l2 == 1 && L.query(1, 1, n, 1, 1).size)l.size += 2;
}
printf("%lld\n",h.size * (r2 - l2 + 1 - l.size) + (r1 - l1 + 1 - h.size) * l.size);
}
}
return 0;
}
B 陵陵曾玩的位运算题
没看懂
upd:没有什么是std解决不了的,褐完了,代码有注释
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c >= '0' && c <= '9');
return x;
}
int lowbit(int x){return x & -x;}
const int maxn = 20, maxm = 131075, bit = 16;
int n, g[maxn], lg[maxm], f[maxn][maxm], f1[maxm], q[maxm], h[maxm], a[maxm];
int main(){
n = read();
for(int i = 1; i <= n; ++i)a[i] = read();
lg[0] = 1;
for(int i = 1; i < (1 << (bit + 1)); ++i)lg[i] = (i & 1) ? -lg[i >> 1] : lg[i >> 1];//容斥系数,后面用,二进制位有奇数/偶数个1
for(int i = bit; i >= 0; --i)g[i] = (1 << i) | g[i + 1]; // 前i位为1
for(int i = bit; i >= 0; --i)
for(int j = 1; j <= n; ++j)
++f[i][a[j] & g[i]];//f[i][j]前i位为j的数的个数
for(int i = 1; i <= n; ++i)++f1[a[i]];//&i == i 的数的个数
for(int i = bit; i >= 0; --i)
for(int j = 0; j < (1 << (bit + 1)); ++j)
f1[j] = ((j >> i) & 1) ? f1[j] : f1[j] + f1[j | 1 << i];//& i 的 i 这一位为0, 那么 j为比i多1个1的数,所有& j == j的数,都是&i==i的数,按位考虑,避免了重复
int Q = read();
for(int ask = 1; ask <= Q; ++ask){
int x = read(), y = read();
int ans = 0, len = 0;
for(int i = 0; i <= bit; ++i)if((x >> i) & 1)q[len++] = 1 << i;//记录二进制位为1的
if(len <= 7){//1比较少,考虑枚举答案
for(int i = (1 << len) - 1; i; --i){//枚举答案
int now = 0, tot = 0;
for(int j = 0; j < len; ++j)
if((i >> j) & 1)now |= q[j];//now为当前答案
else h[1 << (tot++)] = q[j];//这些位置不能是1
int sum = 0;//&x == y的数的个数
for(int k = 0; k < (1 << tot); ++k){
h[k] = h[k ^ lowbit(k)] | h[lowbit(k)];//按照二进制位进行容斥
sum += lg[k] * f1[now | h[k]];//容斥:加上比now多偶数个1的,减去比now多奇数个1的
}
if(sum >= y){ans = now; break;}//找到答案了
else y -= sum;//继续找
}
}else{//0比较少
int tot = 1; h[tot] = 0;//h表示目前需考虑的数
for(int i = bit; i >= 0; --i){
if((x >> i) & 1){// 第 i 位是 1 / 0
int sum = 0;//如果为1, 记录该位为1的数的个数
for(int j = 1; j <= tot; ++j)sum += f[i][h[j] | 1 << i];
if(sum >= y){//如果为1的数多了,那么答案在为1的数里面
ans |= 1 << i;
for(int j = 1; j <= tot; ++j)h[j] |= 1 << i;//更新考虑范围
}else y -= sum;//如果少了,那么在为0的数里面找y - sum大的
}else{
int now = tot;//如果为0,那么1/0都考虑,原来该位都为0,或上(1 << i)该位为1
for(int j = 1; j <= now; ++j)h[++tot] = h[j] | 1 << i;
}
}
}
printf("%d\n",ans);
}
return 0;
}
C 暴力计算
矩阵\((i,j)\)可以理解为从\(i - j\)的最长路,通过倍增处理走\(2^i\)步后的矩阵
然后搞一下就行了
(好像有写挂的地方,但是AC了)
code
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll inf = 1e18 + 1e17;
const int maxn = 105;
int n;
ll m, mp[maxn][maxn], now[maxn][maxn], ls[maxn][maxn];
ll po[maxn][maxn][maxn];
void work(){
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
po[0][i][j] = mp[i][j];
ll mx = -inf;
for(int j = 1; j <= n; ++j)
mx = max(mx, mp[1][j]);
int p = 1;
for(; mx < m; ++p){
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
po[p][i][j] = -inf;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
for(int k = 1; k <= n; ++k)
po[p][i][j] = max(po[p][i][j], po[p - 1][i][k] + po[p - 1][k][j]);
for(int j = 1; j <= n; ++j) mx = max(mx, po[p][1][j]);
if(mx >= m)break;
}
--p;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
now[i][j] = po[p][i][j];
ll ans = 1ll << p;
for( ; p >= 0; --p){
while(1){
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
ls[i][j] = -inf;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
for(int k = 1; k <= n; ++k)
ls[i][j] = max(ls[i][j], now[i][k] + po[p][k][j]);
mx = -inf;
for(int j = 1; j <= n; ++j)mx = max(mx, ls[1][j]);
if(mx >= m)break;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
now[i][j] = ls[i][j];
ans += (1ll << p);
}
}
printf("%lld\n",ans + 1);
}
int main(){
int T; scanf("%d",&T);
for(int ask = 1; ask <= T; ++ask){
scanf("%d%lld",&n,&m);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
scanf("%lld",&mp[i][j]);
if(mp[i][j] == 0)mp[i][j] = -inf;
}
}
work();
}
return 0;
}
D 枚举计算
按照护盾拓扑序考虑,每次\(dij\)到某个点时更新他出边的点,只有当前入度为\(0\)的点才能更新其他点(否则该点\(into\)不对)
处理\(arrive_i\)表示能到达\(i\)号点(不一定能进去)的最早时刻
处理\(into_i\)表示能进去\(i\)号点(不一定能到达)的最早时刻
用 \(dis_i = max(arrive_i, into_i)\)来更新其他点的\(arrive\),用\(into_i = max _{u \in E(-> i )}dis_u\)更新\(into\)
code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 3005;
const int maxm = 70005;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c >= '0' && c <= '9');
return x;
}
int n, m;
struct edge{int to,net,val;}e[maxm];
int head[maxn], tot, rd[maxn], arrive[maxn], dis[maxn], into[maxn];
void add(int u, int v,int w){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
e[tot].val = w;
}
vector<int>g[maxn];
typedef pair<int, int> pii;
priority_queue<pii, vector<pii>, greater<pii> >Q;
queue<int>q;
bool vis[maxn];
int main(){
n = read(), m = read();
for(int i = 1; i <= m; ++i){
int u = read(), v = read(), w = read();
add(u, v, w);
}
for(int i = 1; i <= n; ++i){
rd[i] = read();
for(int j = 1; j <= rd[i]; ++j)
g[read()].push_back(i);
if(rd[i] == 0) q.push(i);
into[i] = 0;
}
memset(arrive, 0x3f, sizeof(arrive));
memset(dis, 0x3f, sizeof(dis));
arrive[1] = dis[1] = 0;
Q.push(pii(0, 1));
while(!Q.empty()){
int x = Q.top().second; Q.pop();
if(vis[x])continue;
vis[x] = 1;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(arrive[v] > dis[x] + e[i].val){
arrive[v] = dis[x] + e[i].val;
dis[v] = max(arrive[v], into[v]);
if(rd[v] == 0)Q.push(pii(dis[v] ,v));
}
}
for(int v : g[x]){
--rd[v];
into[v] = max(into[v], dis[x]);
dis[v] = max(into[v], arrive[v]);
if(rd[v] == 0)Q.push(pii(dis[v], v));
}
}
printf("%d\n", dis[n]);
return 0;
}