A.meeting
场上写了一坨树形dp。。。
dfs序枚举集合点,答案由三部分组成:
A:从父亲往外的节点(多走一步)得到(一边dfs一边更新)
B:从自己的兄弟(多走两步(先到父亲再往下))得到
C:从自己的子树得到
写得极其凌乱。。打了无数的补丁。。
当然最快的做法就是找到最远关键点对,类似于树的直径跑两遍dfs或dfs即可
//树形dp
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 100005
using namespace std;
priority_queue<int> son[N];
vector<int> e[N];
int b[N],fa[N],dis[N],f[N],pt[N];
int res,n,m,i,x,y;
void dfs(int x)
{
int i,nxt,delta;
for (i = 0;i < e[x].size(); i++)
{
nxt = e[x][i];
if (b[nxt] == 1) continue;
b[nxt] = 1;
fa[nxt] = x;
dfs(nxt);
if (pt[nxt] == 1) dis[x] = max(dis[x],1);
if (dis[nxt] > 0 || pt[nxt] == 1) delta = 1; else delta = 0;
dis[x] = max(dis[x],dis[nxt]+delta);
if (dis[nxt] > 0 || pt[nxt] == 1)
son[x].push(dis[nxt]);
}
}
void dfs2(int x)
{
int i,nxt,A,B,C,temp;
if (dis[x] > 0 || pt[x] == 1)
{
A = f[fa[x]];
if (A > 0 || pt[fa[x]] == 1) A++;
if (son[fa[x]].size() <= 1) B = 0; else
{
B = son[fa[x]].top();
if (dis[x] == B/* && son[fa[x]].size() >= 2*/)
{
temp = son[fa[x]].top();son[fa[x]].pop();
B = son[fa[x]].top();
son[fa[x]].push(temp);
}
B += 2;
}
C = dis[x];
res = min(res,max(A,max(B,C)));
f[x] = max(A,B);
}
for (i = 0;i < e[x].size(); i++)
{
nxt = e[x][i];
if (b[nxt] == 1) continue;
b[nxt] = 1;
dfs2(nxt);
}
}
int main()
{
scanf("%d%d",&n,&m);
fo(i,1,n-1)
{
scanf("%d%d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
}
res = 1e9;
fo(i,1,m) {scanf("%d",&x); pt[x] = 1;}
fo(i,1,n) b[i] = 0; b[1] = 1;
dfs(1);
fo(i,1,n) b[i] = 0; b[1] = 1;
dfs2(1);
cout<<res<<endl;
return 0;
}
//树的直径
#include<iostream>
#include<vector>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 200000
using namespace std;
int f[N],a[N],b[N];
int len,pt,n,m,i,x,y;
vector<int> e[N];
void dfs(int x,int d)
{
int i;
if (f[x] && d > len)
{
len = d;
pt = x;
}
for (i = 0;i < e[x].size(); i++)
if (b[e[x][i]] == 0)
{
b[e[x][i]] = 1;
dfs(e[x][i],d+1);
}
}
int main()
{
scanf("%d%d",&n,&m);
fo(i,1,n-1)
{
scanf("%d%d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
}
fo(i,1,m) scanf("%d",&a[i]);
fo(i,1,m) f[a[i]] = 1;
fo(i,1,n) b[i] = 0; b[1] = 1; len = 0;
dfs(1,0);
fo(i,1,n) b[i] = 0; b[pt] = 1; len = 0;
dfs(pt,0);
cout<<len/2+(len%2)<<endl;
return 0;
}
B.xor
核心问题就是线性基求交
假设有两个线性基
A
,
B
A,B
A,B,设一个新的线性基
X
X
X,一开始
X
=
A
X=A
X=A,然后不断插入
B
B
B的元素。对于
B
i
B_i
Bi,如果它能被
X
X
X线性表示,那么将“
A
A
A中用来组成
B
i
B_i
Bi的元素”加入答案线性基,否则将这个线性基加入
X
X
X。(也就是说
X
X
X中的元素不但要记录值,还要记录由
A
A
A贡献的部分)
然后。。没了
线段树维护区间线性基
注意单次线性基求交的时间复杂度是
O
(
3
2
2
)
≈
O
(
l
o
g
2
N
)
O(32^2) \approx O(log^2N)
O(322)≈O(log2N)
线段树预处理需要求
O
(
N
)
O(N)
O(N)次交,没有问题
但是线段树合并的时候,如果合并
O
(
l
o
g
N
)
O(logN)
O(logN)个区间,时间复杂度是3个log
实际上我们可以直接询问这
O
(
l
o
g
N
)
O(logN)
O(logN)个区间能否表示出
x
x
x
单次线性基查询的时间复杂度是
O
(
32
)
O(32)
O(32)
总时间复杂度是两个log
#include<iostream>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define N 500005
#define ll long long
using namespace std;
ll basis[N*4][32];
int n,m,l,r;
ll x;
void insert(int rt,ll x)
{
int i;
fd(i,31,0)
if (x & (1<<i))
if (basis[rt][i] == 0)
{
basis[rt][i] = x;
return;
} else x = x ^ basis[rt][i];
}
void merge(int a,int b,int c)
{
int i,j; ll u,k;
ll x[100],y[100];
fo(i,0,31) x[i] = y[i] = basis[a][i];
fo(i,0,31)
if (basis[b][i])
{
u = basis[b][i]; k = 0;
fd(j,31,0)
if (u & (1<<j))
if (x[j])
{
u = u ^ x[j];
k = k ^ y[j];
} else
{
x[j] = u;
y[j] = k;
break;
}
if (u == 0) insert(c,k);
}
}
void build(int rt,int l,int r)
{
int sz; ll x;
if (l == r)
{
scanf("%d",&sz);
while (sz--) {scanf("%lld",&x); insert(rt,x);}
return;
}
int mid = (l + r) >> 1;
build(rt<<1,l,mid); build(rt<<1|1,mid+1,r);
merge(rt<<1,rt<<1|1,rt);
}
bool check(int rt,ll x)
{
int i;
fd(i,31,0)
if (x & (1 << i))
if (basis[rt][i])
x = x ^ basis[rt][i];
else return false;
return true;
}
bool query(int rt,int l,int r,int L,int R,ll x)
{
if (L <= l && r <= R) return check(rt,x);
int mid = (l + r) >> 1;
bool res = true;
if (L <= mid) res = res && query(rt<<1,l,mid,L,R,x);
if (mid+1 <= R) res = res && query(rt<<1|1,mid+1,r,L,R,x);
return res;
}
int main()
{
scanf("%d%d",&n,&m);
build(1,1,n);
while (m--)
{
scanf("%d%d%lld",&l,&r,&x);
if (query(1,1,n,l,r,x)) printf("YES\n"); else printf("NO\n");
}
}
C.sequence
我这题用了一个非常慢的两个log的写法。。。。。。
第一个log是查询最小值的位置,然后查询左右子区间的前缀和最大/小值
第二个log是递归的log
ST表竟然卡内存
最后用读优才卡过去
std竟然是
O
(
n
)
O(n)
O(n)
实际上可以枚举每个数作为最小值,用单调栈找到左右区间范围
然后用笛卡尔树直接找到区间最值
#include <bits/stdc++.h>
using namespace std;
namespace fastIO
{
#define BUF_SIZE 100000
bool IOerror=0;
inline char nc()
{
static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE;
if (p1==pend)
{
p1=buf;
pend=buf+fread(buf,1,BUF_SIZE,stdin);
if (pend==p1)
{
IOerror=1;
return -1;
}
}
return *p1++;
}
inline bool blank(char ch)
{
return ch==' ' || ch=='\n' || ch=='\r' || ch=='\t';
}
inline void read(int &x)
{
char ch; int f=1;
while (blank(ch=nc()));
if (IOerror) return;
if (ch=='-') {ch=nc(); f = -1;}
for (x=(ch-'0')*f;(ch=nc())>='0' && ch<='9'; x=x*10+(ch-'0')*f);
}
#undef BUF_SIZE
}
using namespace fastIO;
typedef long long ll;
const int maxn=3e6+5;
ll pre[maxn];
int a[maxn],b[maxn],pos[maxn*4];
ll mx[maxn*4],mn[maxn*4];
int c[maxn*4];
int bit[maxn];
int n;
void build2(int rt,int l,int r)
{
if (l == r) {c[rt] = a[l]; pos[rt] = l; return;}
int mid = (l + r) >> 1;
build2(rt<<1,l,mid); build2(rt<<1|1,mid+1,r);
if (c[rt<<1] < c[rt<<1|1])
{
c[rt] = c[rt<<1];
pos[rt] = pos[rt<<1];
} else
{
c[rt] = c[rt<<1|1];
pos[rt] = pos[rt<<1|1];
}
}
int Qmin(int rt,int l,int r,int L,int R)
{
if (L <= l && r <= R) return pos[rt];
int mid = (l + r) >> 1;
int x,y; x=y=0;
if (L <= mid) x=Qmin(rt<<1,l,mid,L,R);
if (mid+1 <= R) y=Qmin(rt<<1|1,mid+1,r,L,R);
if (x == 0) return y; else if (y == 0) return x; else
if (a[x] < a[y]) return x; else return y;
}
void build(int rt,int l,int r)
{
if (l == r) {mx[rt] = mn[rt] = pre[l]; return;}
int mid = (l + r) >> 1;
build(rt<<1,l,mid); build(rt<<1|1,mid+1,r);
mx[rt] = max(mx[rt<<1],mx[rt<<1|1]);
mn[rt] = min(mn[rt<<1],mn[rt<<1|1]);
}
ll qmax(int rt,int l,int r,int L,int R)
{
if (L <= l && r <= R) return mx[rt];
int mid = (l + r) >> 1;
ll res = -1e18;
if (L <= mid) res = max(res,qmax(rt<<1,l,mid,L,R));
if (mid+1 <= R) res = max(res,qmax(rt<<1|1,mid+1,r,L,R));
return res;
}
ll qmin(int rt,int l,int r,int L,int R)
{
if (L <= l && r <= R) return mn[rt];
int mid = (l + r) >> 1;
ll res = 1e18;
if (L <= mid) res = min(res,qmin(rt<<1,l,mid,L,R));
if (mid+1 <= R) res = min(res,qmin(rt<<1|1,mid+1,r,L,R));
return res;
}
const int delta=1e6;
ll ans=-1e18;
void solve(int l,int r)
{
if (l>r) return;
int p=Qmin(1,1,n,l,r);
int k = a[p];
if (k>0)
{
ll rp=qmax(1,1,n,p,r);
ll lp;
if (p==1) lp=0;
else lp=qmin(1,1,n,max(l-1,1),p-1);
ans=max(ans,1ll*k*(rp-lp));
}
else if (k<0)
{
ll rp=qmin(1,1,n,p,r);
ll lp;
if (p==1) lp=0;
else lp=qmax(1,1,n,max(l-1,1),p-1);
ans=max(ans,1ll*k*(rp-lp));
}
else ans=max(ans,0ll);
solve(l,p-1);
solve(p+1,r);
}
int main()
{
//freopen("1.in","r",stdin);
read(n);
for (int i=1;i<=n;i++)
read(a[i]);
for (int i=1;i<=n;i++)
read(b[i]);
build2(1,1,n);
//init(n,a);
for (int i=1;i<=n;i++)
pre[i]=pre[i-1]+b[i];
build(1,1,n);
solve(1,n);
printf("%lld\n",ans);
return 0;
}
D.triples I
考虑奇数位上的1和偶数位上的1
奇数位模3余2,偶数位模3余1
考虑若干种情况…
1:只有奇数位或者偶数位。实际上是类似区间交的情况,假设有17个,那么可以把前15个数or起来作为ans1,多的2个数从前面拉过来一个数凑成三个数,or起来作为ans2
2:同时有奇数位和偶数位。先两两配对作为ans1,然后把多的那部分全部or起来。假设多的是奇数位,如果多的个数是3的倍数,那么刚好;如果多一个,那么再找一个配对过的偶数位;如果多两个,那么再找一个配对过的奇数位。
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
long long T,x,op,bit,even,odd,eveneven,oddodd,res1,res2,delta,i;
long long a[100],b[100];
int main()
{
scanf("%d",&T);
while (T--)
{
odd = even = 0;
scanf("%lld",&x);
op = 1;//odd=1
bit = 0;
while (x)
{
if (x&1)
{
if (bit % 2 == 0)
{
even++;
a[even] = 1ll << bit;
} else
{
odd++;
b[odd] = 1ll << bit;
}
}
bit++; x /= 2;
}
oddodd = odd;
eveneven = even;
if (odd == 0)
{
res1 = res2 = 0;
delta = even % 3;
fo(i,1,even-delta) res1 = res1 | a[i];
if (delta)
fo(i,even-2,even) res2 = res2 | a[i];
if (res2 == 0) cout<<1<<" "<<res1<<endl;
else if (res1 == 0) cout<<1<<" "<<res2<<endl;
else cout<<2<<" "<<res1<<" "<<res2<<endl;
//cout<<(res1|res2)<<endl;
continue;
}
if (even == 0)
{
res1 = res2 = 0;
delta = odd % 3;
fo(i,1,odd-delta) res1 = res1 | b[i];
if (delta)
fo(i,odd-2,odd) res2 = res2 | b[i];
if (res2 == 0) cout<<1<<" "<<res1<<endl;
else if (res1 == 0) cout<<1<<" "<<res2<<endl;
else cout<<2<<" "<<res1<<" "<<res2<<endl;
//cout<<(res1|res2)<<endl;
continue;
}
res1 = 0;
while (odd > 0 && even > 0)
{
res1 = res1 | a[even];
res1 = res1 | b[odd];
even--;
odd--;
}
res2 = 0;
if (odd > 0)
{
delta = odd % 3;
while (odd) {res2 = res2 | b[odd]; odd--;}
if (delta == 1) res2 = res2 | a[1];
if (delta == 2) res2 = res2 | b[oddodd];
}
if (even > 0)
{
delta = even % 3;
while (even) {res2 = res2 | a[even]; even--;}
if (delta == 1) res2 = res2 | b[1];
if (delta == 2) res2 = res2 | a[eveneven];
}
if (res2 == 0) cout<<1<<" "<<res1<<endl;
else if (res1 == 0) cout<<1<<" "<<res2<<endl;
else cout<<2<<" "<<res1<<" "<<res2<<endl;
//cout<<(res1|res2)<<endl;
}
return 0;
}
E.triples II
warning:以下题解的奇偶性如果和程序不符不要怪我。。。
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示最多用i个奇数位和j个偶数位能够凑出的3的倍数的个数
这里是“最多”,所以要去掉严格小于的情况
这其实是一个容斥原理的模型
比如要求x个奇数位和y个偶数位
不严格的讲就是:
首先答案是
f
[
x
]
[
y
]
f[x][y]
f[x][y],然后去掉非法的
f
[
x
−
1
]
[
y
]
f[x-1][y]
f[x−1][y]和
f
[
x
]
[
y
−
1
]
f[x][y-1]
f[x][y−1],然后还得加上多算的
f
[
x
]
[
y
]
f[x][y]
f[x][y]…然后算每一部分都要继续容斥下去
因此答案:
a
n
s
=
∑
0
≤
i
≤
x
,
0
≤
j
≤
y
p
o
w
(
f
[
i
]
[
j
]
,
n
)
C
x
i
C
y
j
s
i
g
(
i
,
j
)
ans=\sum _{ 0\le i\le x,0\le j\le y }^{ }{ pow(f[i][j],n){ C }_{ x }^{ i }{ C }_{ y }^{ j }sig(i,j) }
ans=∑0≤i≤x,0≤j≤ypow(f[i][j],n)CxiCyjsig(i,j)
其中,若
i
+
j
i+j
i+j的奇偶性和
x
+
y
x+y
x+y相同则
s
i
g
=
1
sig=1
sig=1,否则等于
−
1
-1
−1
#include<iostream>
#include<vector>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 200000
#define ll long long
#define MOD 998244353
using namespace std;
ll c[100][100],f[100][100];
int T,op,x,y,i,j;
ll n,a,res,res_t;
void init_c()
{
int i,j;
fo(i,0,64)
{
c[i][0] = 1;
fo(j,1,i) c[i][j] = (c[i-1][j] + c[i-1][j-1]) % MOD;
}
}
void init()
{
int i,j,i1,j1;
fo(i,0,64)
fo(j,0,64)
{
fo(i1,0,i)
fo(j1,0,j)
if ((i1 + j1 * 2) % 3 == 0)
f[i][j] = (f[i][j] + (c[i][i1] * c[j][j1]) % MOD) % MOD;
}
}
ll qp(ll a,ll x)
{
ll res = 1; ll e = a;
while (x)
{
if (x&1) res = (res * e) % MOD;
e = (e * e) % MOD;
x >>= 1;
}
return res;
}
int main()
{
init_c();
init();
scanf("%d",&T);
while (T--)
{
scanf("%lld%lld",&n,&a);
op = x = y = 0;
while (a)
{
if (a&1) if (op==0) x++; else y++;
a >>= 1;
op = 1 - op;
}
res = 0;
fo(i,0,x)
fo(j,0,y)
{
res_t = 1;
res_t = (c[x][i] * c[y][j]) % MOD;
res_t = (res_t * qp(f[i][j],n)) % MOD;
if ((i+j) % 2 == (x+y) % 2) res_t *= 1; else res_t *= -1;
res = (res + res_t + MOD) % MOD;
}
cout<<res<<endl;
}
return 0;
}
J.free
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示走到
i
i
i这个点,并且最多有
j
j
j条边免费的最短路
那么
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
]
[
j
−
1
]
,
m
i
n
(
f
[
x
]
[
j
−
1
]
,
f
[
x
]
[
j
]
+
e
)
)
f[i][j]=min(f[i][j-1],min(f[x][j-1],f[x][j]+e))
f[i][j]=min(f[i][j−1],min(f[x][j−1],f[x][j]+e)),其中
e
e
e表示
x
x
x走到
i
i
i距离
按照
j
j
j从小到大转移
每次初始化
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
]
[
j
−
1
]
,
f
[
x
]
[
j
−
1
]
)
f[i][j]=min(f[i][j-1],f[x][j-1])
f[i][j]=min(f[i][j−1],f[x][j−1]),然后跑最短路
K.number
f
[
i
]
[
0
/
1
/
2
]
f[i][0/1/2]
f[i][0/1/2]表示第
i
i
i个数必取,前面组成的所有数模3余0/1/2的个数
如果
s
[
i
+
1
]
=
s
[
i
+
2
]
=
0
s[i+1]=s[i+2]=0
s[i+1]=s[i+2]=0,那么把
f
[
i
]
[
0
]
f[i][0]
f[i][0]加入答案
最后加入
′
0
′
'0'
′0′和
′
0
0
′
'00'
′00′的情况
#include<bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 200000
using namespace std;
long long n,i,j,t;
long long res;
long long a[N],f[N][5];
char ch[N];
int main()
{
scanf("%s",ch+1);
n = strlen(ch+1);
fo(i,1,n) a[i] = ch[i] - '0';
fo(i,1,n) if (a[i] == 0) res++;
fo(i,1,n-1) if (a[i] == 0 && a[i+1] == 0) res++;
fo(i,1,n)
{
fo(j,0,2)
{
t = (a[i] + j) % 3;
f[i][t] += f[i-1][j];
}
f[i][a[i]%3] += 1;
}
fo(i,1,n-1)
if (a[i] == 0 && a[i+1] == 0)
res += f[i-1][0];
cout<<res<<endl;
return 0;
}