2023牛客+杭电补题和总结记录(前半)
牛客第一场
\(H.Matches\)
赛后卡过了非正解,记一下优化过程,感觉还是挺有用
- 缩短线段树长度。区间信息只记在左端点处,右端点只用于查询,因此在离散化的时候就去掉右端点,线段树只开左端点个数的长度。不影响查询时的范围。
- 线段树函数用记录左右端点代替传参。据说不同的机器在这里有差异,这题用传参的写法会卡不过去。
- 快读,去除冗余代码
H.Matches(线段树解)
#include
#define ll long long
using namespace std;
const int N = 1e6 + 9;
int n, a[N], b[N], cnt, d[N << 1], sum;
ll tmp, ans;
struct node {
int l, r, tag, L, R;
}c[N << 1];
struct tree {
int l, r;
ll siz, num;
}t[2][N << 2];
bool cmp(node x, node y) {
return x.R < y.R;
}
inline int read() {
int x = 0, f = 1;char ch = getchar();
while (ch < '0' || ch>'9') { if (ch == '-')f = -1;ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar(); }
return x * f;
}
void change(int p, int pos, ll L, ll R, int tag) {
if (t[tag][p].l == t[tag][p].r) {
t[tag][p].siz = max(t[tag][p].siz, R - L);
t[tag][p].num = max(t[tag][p].num, R);
return;
}
if (pos <= t[tag][p << 1].r)change(p << 1, pos, L, R, tag);
else change(p << 1 | 1, pos, L, R, tag);
t[tag][p].siz = max(t[tag][p << 1].siz, t[tag][p << 1 | 1].siz);
t[tag][p].num = max(t[tag][p << 1].num, t[tag][p << 1 | 1].num);
}
ll ask(int p, int L, int R, int op, int tag) {
if (L <= t[tag][p].l && t[tag][p].r <= R) {
if (op == 0)return t[tag][p].num;
else return t[tag][p].siz;
}
ll val = -1e18;
if (L <= t[tag][p << 1].r)val = max(val, ask(p << 1, L, R, op, tag));
if (R >= t[tag][p << 1 | 1].l)val = max(val, ask(p << 1 | 1, L, R, op, tag));
return val;
}
void build(int p, int l, int r) {
t[0][p].l = t[1][p].l = l, t[0][p].r = t[1][p].r = r;
t[0][p].num = t[1][p].num = -1e18;
t[0][p].siz = t[1][p].siz = -1e18;
if (l == r)return;
int mid = (l + r) >> 1;
build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
}
int main()
{
n = read();
for (int i = 1;i <= n;i++)a[i] = read();
for (int i = 1;i <= n;i++)b[i] = read(), d[i] = min(b[i], a[i]);
sort(d + 1, d + n + 1);
sum = unique(d + 1, d + n + 1) - d - 1;
for (int i = 1;i <= n;i++) {
tmp += abs(a[i] - b[i]);
if (a[i] < b[i]) {
c[++cnt].L = a[i], c[cnt].R = b[i];
c[cnt].tag = 0;
c[cnt].l = lower_bound(d + 1, d + sum + 1, a[i]) - d;
c[cnt].r = upper_bound(d + 1, d + sum + 1, b[i]) - d - 1;
}
else {
c[++cnt].L = b[i], c[cnt].R = a[i];
c[cnt].tag = 1;
c[cnt].l = lower_bound(d + 1, d + sum + 1, b[i]) - d;
c[cnt].r = upper_bound(d + 1, d + sum + 1, a[i]) - d - 1;
}
}
build(1, 1, sum);
sort(c + 1, c + cnt + 1, cmp);
for (int i = 1;i <= cnt;i++) {
change(1, c[i].l, c[i].L, c[i].R, c[i].tag);
ans = max(ans, 2 * (ask(1, 1, c[i].l, 0, c[i].tag ^ 1) - c[i].L));
ans = max(ans, 2 * ask(1, c[i].l, c[i].r, 1, c[i].tag ^ 1));
// printf("tag:%d ",c[i].tag);
// printf("l:%d r:%d ans:%lld %lld %lld\n",c[i].L,c[i].R,ans,ask(1,1,sum,1,c[i].l,0,c[i].tag^1),ask(1,1,sum,c[i].l,c[i].r,1,c[i].tag^1));
}
printf("%lld\n", tmp - ans);
return 0;
}
\(update:\)
对于某个区间,只需要在另一种区间里找右端点大于它的右端点的区间中最小的左端点,然后更新答案就可以了。被覆盖的情况可以由另一种区间反过来处理一遍更新到。
所求为区间互相覆盖的最大长度,围绕这个目的再去考虑做法。
H.Matches(优美的扫描线解)
#include
#define ll long long
using namespace std;
const int N = 1e6 + 9;
int n, a[N], b[N], cnt, d[N << 1], num;
ll tmp, ans = -1e16, sum;
struct node {
ll tag, L, R;
}c[N << 1], e[N << 1];
bool cmp(node x, node y) {
return x.R > y.R;
}
inline int read() {
int x = 0, f = 1;char ch = getchar();
while (ch < '0' || ch>'9') { if (ch == '-')f = -1;ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar(); }
return x * f;
}
int main()
{
n = read();
for (int i = 1;i <= n;i++)a[i] = read();
for (int i = 1;i <= n;i++)b[i] = read();
for (int i = 1;i <= n;i++) {
sum += abs(a[i] - b[i]);
if (a[i] < b[i]) {
c[++cnt].L = a[i], c[cnt].R = b[i];
}
else {
e[++num].L = b[i], e[num].R = a[i];
}
}
sort(c + 1, c + cnt + 1, cmp);
sort(e + 1, e + num + 1, cmp);
int pos = 1;
ll tmp = 1e16;
for (int i = 1;i <= cnt;i++) {
while (pos <= num && e[pos].R >= c[i].R) {
tmp = min(tmp, e[pos].L);
pos++;
}
ans = max(ans, c[i].R - max(c[i].L, tmp));
}
pos = 1, tmp = 1e16;
for (int i = 1;i <= num;i++) {
while (pos <= cnt && c[pos].R >= e[i].R) {
tmp = min(tmp, c[pos].L);
pos++;
}
ans = max(ans, e[i].R - max(e[i].L, tmp));
}
printf("%lld\n", min(sum, sum - 2 * ans));
return 0;
}
杭电第一场
\(1010.Easy\ problem\ I\)
一度怀疑以现在的能力是不是不该补这题
又(?)是一道线段树。这次是正统的线段树考核题了,涉及到了比较巧妙的线段树维护的思想,以及懒标记的使用。
将绝对值拆开,发现当某次 \(a_i<x_j\) 后,此后它会一直保持 \(a_i'<x_{j+...}\) 的形式。在这种情况下,该数实际上为 \(a_i\) 不断取反,且累加上 \(x_i-x_{i+1}+x_{i+2}...\)
考虑用线段树分别维护 \(a_i>x_j\) 和 \(a_i<x_j\)(等于归于其中一种)的情况。前一种直接在线段树上区间减,同时维护最小值。后一种要维护区间是否取反、取反和没取反的数字个数与数字和、区间累加 \(x\) 的总和。每次取反时,交换两种数字的信息,同时累加 \(x\) 的总和也要取反再加上新 \(x\)。
某个数字第一次变为 \(a_i<x_j\) 时,暴力地加入另一棵树。
一些坑点和信息写在注释里。
1010.Easy problem I(线段树)
#include
#define ll long long
using namespace std;
const int N=2e5+10;
int t,n,m,flag;
ll a[N];
struct tree{
int l,r,tag1,tag0,num,op;
ll tag,sum,sum1,sum0,minn;
}b[2][N<<3];
int read(){
int c=0;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar());
for(;ch>='0'&&ch<='9';c=c*10+ch-'0',ch=getchar());
return c;
}
void pushup(int p){
b[0][p].minn=min(b[0][p<<1].minn,b[0][p<<1|1].minn);
b[0][p].sum=b[0][p<<1].sum+b[0][p<<1|1].sum;
b[0][p].num=b[0][p<<1].num+b[0][p<<1|1].num;
b[1][p].sum1=b[1][p<<1].sum1+b[1][p<<1|1].sum1;
b[1][p].sum0=b[1][p<<1].sum0+b[1][p<<1|1].sum0;
b[1][p].tag1=b[1][p<<1].tag1+b[1][p<<1|1].tag1;
b[1][p].tag0=b[1][p<<1].tag0+b[1][p<<1|1].tag0;
}
void pushdown(int p){
if(b[0][p].tag){
ll val=b[0][p].tag;
b[0][p<<1].tag+=val;
b[0][p<<1|1].tag+=val;
b[0][p<<1].sum-=b[0][p<<1].num*val;
b[0][p<<1|1].sum-=b[0][p<<1|1].num*val;
b[0][p<<1].minn-=val;
b[0][p<<1|1].minn-=val;
b[0][p].tag=0;
}
if(b[1][p].tag){
ll val=b[1][p].tag;
if(b[1][p].op){//如果需要取反
swap(b[1][p<<1].sum0,b[1][p<<1].sum1);
swap(b[1][p<<1].tag0,b[1][p<<1].tag1);
swap(b[1][p<<1|1].sum0,b[1][p<<1|1].sum1);
swap(b[1][p<<1|1].tag0,b[1][p<<1|1].tag1);
b[1][p<<1].tag=-b[1][p<<1].tag,b[1][p<<1|1].tag=-b[1][p<<1|1].tag;
b[1][p<<1].op^=1,b[1][p<<1|1].op^=1;
b[1][p].op=0;
}
b[1][p<<1].tag+=val;
b[1][p<<1|1].tag+=val;
b[1][p<<1].sum0+=b[1][p<<1].tag0*val;
b[1][p<<1].sum1-=b[1][p<<1].tag1*val;
b[1][p<<1|1].sum0+=b[1][p<<1|1].tag0*val;
b[1][p<<1|1].sum1-=b[1][p<<1|1].tag1*val;
b[1][p].tag=0;
}
}
void build(int p,int l,int r){
b[0][p].l=b[1][p].l=l;
b[0][p].r=b[1][p].r=r;
b[0][p<<1].sum=b[0][p<<1].sum0=b[0][p<<1].sum1=b[0][p<<1].tag=b[0][p<<1].tag0=b[0][p<<1].tag1=b[0][p<<1].op=0;
b[1][p<<1].sum=b[1][p<<1].sum0=b[1][p<<1].sum1=b[1][p<<1].tag=b[1][p<<1].tag0=b[1][p<<1].tag1=b[1][p<<1].op=0;
b[0][p<<1|1].sum=b[0][p<<1|1].sum0=b[0][p<<1|1].sum1=b[0][p<<1|1].tag=b[0][p<<1|1].tag0=b[0][p<<1|1].tag1=b[0][p<<1|1].op=0;
b[1][p<<1|1].sum=b[1][p<<1|1].sum0=b[1][p<<1|1].sum1=b[1][p<<1|1].tag=b[1][p<<1|1].tag0=b[1][p<<1|1].tag1=b[1][p<<1|1].op=0;
if(l==r){
b[0][p].minn=b[0][p].sum=a[l];
b[0][p].num=1;
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
pushup(p);
}
void add(int p,int pos,ll x){
if(b[1][p].l==b[1][p].r){
b[1][p].sum0=0,b[1][p].sum1=-x;//一开始设置成反的
b[1][p].tag0=0,b[1][p].tag1=1;
return;
}
pushdown(p);
int mid=(b[1][p].l+b[1][p].r)>>1;
if(pos<=mid)add(p<<1,pos,x);
else add(p<<1|1,pos,x);
pushup(p);
}
void work(int p,ll x){
if(b[0][p].l==b[0][p].r){
add(1,b[0][p].l,b[0][p].sum);
b[0][p].num=0;
b[0][p].minn=1e16;
b[0][p].sum=0;
return;
}
pushdown(p);
if(b[0][p<<1].minn=b[0][p<<1|1].l)change0(p<<1|1,l,r,x);
pushup(p);
}
void change1(int p,int l,int r,ll x){
if(!(b[1][p].tag0+b[1][p].tag1))return;
if(l<=b[1][p].l&&b[1][p].r<=r){
swap(b[1][p].sum0,b[1][p].sum1);
swap(b[1][p].tag0,b[1][p].tag1);
b[1][p].tag=-b[1][p].tag+x;//x的累加也是取反再加上
b[1][p].op^=1;
b[1][p].sum0+=x*b[1][p].tag0;
b[1][p].sum1-=x*b[1][p].tag1;
return;
}
pushdown(p);
if(l<=b[1][p<<1].r)change1(p<<1,l,r,x);
if(r>=b[1][p<<1|1].l)change1(p<<1|1,l,r,x);
pushup(p);
}
ll ask0(int p,int l,int r){
if(!b[0][p].num)return 0;
if(l<=b[0][p].l&&b[0][p].r<=r){
return b[0][p].sum;
}
ll val=0;
pushdown(p);
if(l<=b[0][p<<1].r)val+=ask0(p<<1,l,r);
if(r>=b[0][p<<1|1].l)val+=ask0(p<<1|1,l,r);
pushup(p);
return val;
}
ll ask1(int p,int l,int r){
if(!(b[1][p].tag0+b[1][p].tag1))return 0;
if(l<=b[1][p].l&&b[1][p].r<=r){
return b[1][p].sum0-b[1][p].sum1;
}
ll val=0;
pushdown(p);
if(l<=b[1][p<<1].r)val+=ask1(p<<1,l,r);
if(r>=b[1][p<<1|1].l)val+=ask1(p<<1|1,l,r);
pushup(p);
return val;
}
int main()
{
// freopen("1010.in","r",stdin);
// freopen("my.txt","w",stdout);
t=read();
while(t--){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
b[0][1].sum=b[0][1].sum0=b[0][1].sum1=b[0][1].tag=b[0][1].tag0=b[0][1].tag1=b[0][1].op=0;
b[1][1].sum=b[1][1].sum0=b[1][1].sum1=b[1][1].tag=b[1][1].tag0=b[1][1].tag1=b[1][1].op=0;
build(1,1,n);
for(int i=1,op,l,r,x;i<=m;i++){
op=read(),l=read(),r=read();
if(op==1){
x=read();
change0(1,l,r,x);
change1(1,l,r,x);
}
else{
printf("%lld\n",ask0(1,l,r)+ask1(1,l,r));
}
}
}
return 0;
}
杭电第二场
\(1007.foreverlasting\ and\ fried-chicken\)
一些需要提醒自己的记录:
应该拿出来重点批评自己,引以为戒的一场比赛。这题本身应该是赛场题,如果写出来的话我们队会拿到至今为止最好的赛场成绩,但是这道简单题被坑在我手里了。心态的问题很大,卡到最后一个小时的时候就去想“我们队就差我这道题”,压力槽是该清一下了。明明想到了bitset,队友也给出了建议说尝试一下比较好,但是我莫名其妙非常自信是算法错了,也没有冷静地和队友沟通。
以后比赛绝不能再出现这种情况。
要统计每两个点之间的长度为 \(2\) 的路径,两个点都是确定的,实际上这里已经把可处理的方法限制得比较狭窄了,再去考虑算法整体有问题也属于是逻辑比较混乱。
用 \(bitset\) 来找到两个点相连点中的重合部分,复杂度是 \(O(\frac{n^3}{w})\)。
1007.foreverlasting and fried-chicken
#include
#define ll long long
using namespace std;
const int N=1010,M=1e6+10,mod=1e9+7;
int t,n,m,d[N],cnt[N],tim,pos[N],vis[N],num,v[N];
int ver[M<<1],head[N],tot,Next[M<<1];
ll f[N],inv[N];
bitsetb[N],B;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
inline ll pw(ll x, ll p){
ll val=1;
while(p){
if(p&1)val=val*x%mod;
x=x*x%mod;
p>>=1;
}
return val;
}
inline ll C(int n,int m){
if(n=1;i--)inv[i]=inv[i+1]*(i+1)%mod;
while(t--){
n=read(),m=read();
for(int i=1;i<=n;i++)b[i].reset(),d[i]=0;
for(int i=1,x,y;i<=m;i++){
x=read(),y=read();
d[x]++,d[y]++;
b[x][y]=b[y][x]=1;
}
ll ans=0;
for(int i=1,y;i<=n;i++){
if(d[i]<6)continue;
for(int j=1;j<=n;j++){
if(i==j)continue;
B=b[i]&b[j];
int tmp=B.count();
if(tmp>=4&&d[i]-4-b[i][j]>=2){
ans=(ans+C(tmp,4)*C(d[i]-4-b[i][j],2)%mod)%mod;
}
}
}
printf("%lld\n",ans);
}
return 0;
}
\(1012.Coin\)
一道拆点建图的网络流。
每个人在有操作涉及到自己的时候拆出新点,这一组新点之间互相连流量为 \(1\) 的边,拆出的点按时间从前往后连边。\(s\) 向每个人连流量为 \(1\) 的边,朋友向 \(t\) 连流量为 \(a_i\) 的边。跑最大流获得答案。
1012.Coin
#include
using namespace std;
const int N=2e4+10,inf=1e9+10;
int T,n,m,k,pre[N],a[N],s,t,cnt;
int cur[N],d[N];
int ver[N<<2],head[N<<1],tot=1,edge[N<<2],Next[N<<2];
void add(int x,int y,int z){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
ver[++tot]=x;
Next[tot]=head[y];
head[y]=tot;
edge[tot]=0;
}
bool bfs(){
for(int i=1;i<=cnt;i++)cur[i]=head[i],d[i]=0;
d[s]=1;
queueq;
q.push(s);
while(q.size()){
int x=q.front();
q.pop();
// printf("x:%d\n",x);
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(!d[y]&&edge[i]){
d[y]=d[x]+1;
q.push(y);
}
}
}
return (d[t]!=0);
}
int dinic(int x,int limit){
if(x==t||!limit)return limit;
int flow=0,f;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(d[y]==d[x]+1&&(f=dinic(y,min(limit,edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
if(!limit)break;
}
}
return flow;
}
int main()
{
// freopen("1012.txt","r",stdin);
scanf("%d",&T);
while(T--){
for(int i=1;i<=cnt;i++)head[i]=0;
scanf("%d%d%d",&n,&m,&k);
s=n+1,t=cnt=n+2;
tot=1;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]),pre[i]=s;
}
for(int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y);
add(pre[x],++cnt,pre[x]==s?1:a[x]);
pre[x]=cnt;
add(pre[y],++cnt,pre[y]==s?1:a[y]);
pre[y]=cnt;
add(pre[x],pre[y],1);
add(pre[y],pre[x],1);
}
for(int i=1,x;i<=k;i++){
scanf("%d",&x);
add(pre[x],t,a[x]);
}
int flow=0;
while(bfs()){
flow+=dinic(s,inf);
// printf("?\n");
}
printf("%d\n",flow);
}
return 0;
}
清华预选赛
这场的两道题也写在这里吧
\(F.Recombination\)
赛场上对于题意理解产生了问题。满足条件的拼合串不仅不能组成原串,也不能组成原创的循环串。
于是哈希,记下每种半串的数量,然后减去拼成原串循环串的方案数。
注意这题的串数会哈希冲突,所以上双哈希。
F.Recombination
#include
#define ll long long
using namespace std;
const int N = 2e6 + 10, p = 277, p1 = 131, mod = 1336363663, mod1 = 998244353;
int n;
ll length, has[N], ans, tag[N], has_odd[N], has_even[N];
ll length1, has1[N], tag1[N], has_odd1[N], has_even1[N];
map, ll>mp1, mp2, mp;
char s[N];
int main()
{
scanf("%d", &n);
scanf("%s", s + 1);
length = length1 = 1;
for (int i = 1;i < n / 2;i++)length = length * p % mod, length1 = length1 * p1 % mod1;
for (int i = 1;i <= n;i++)s[n + i] = s[i];
ll odd = 0, odd1 = 0, even = 0, even1 = 0;
tag[0] = tag1[0] = 1;
for (int i = 1;i <= n;i++) {
if (i & 1)odd = (odd * p % mod + s[i]) % mod, odd1 = (odd1 * p1 % mod1 + s[i]) % mod1;
else even = (even * p % mod + s[i]) % mod, even1 = (even1 * p1 % mod1 + s[i]) % mod1;
tag[i] = tag[i - 1] * p % mod, tag1[i] = tag1[i - 1] * p1 % mod1;
}
has[1] = (odd * tag[n / 2] % mod + even) % mod, has1[1] = (odd1 * tag1[n / 2] % mod1 + even1) % mod1;
mp[make_pair(has[1], has1[1])] = 1;
mp1[make_pair(odd, odd1)]++, mp2[make_pair(even, even1)]++;
has_odd[1] = odd, has_odd1[1] = odd1, has_even[1] = even, has_even1[1] = even1;
for (int i = 2;i <= n;i++) {
swap(odd, even);
swap(odd1, even1);
even = (even - length * s[i - 1] % mod + mod) % mod;
even = (even * p % mod + s[i + n - 1]) % mod;
even1 = (even1 - length1 * s[i - 1] % mod1 + mod1) % mod1;
even1 = (even1 * p1 % mod1 + s[i + n - 1]) % mod1;
has[i] = (odd * tag[n / 2] % mod + even) % mod;
has1[i] = (odd1 * tag1[n / 2] % mod1 + even1) % mod1;
mp1[make_pair(odd, odd1)]++, mp2[make_pair(even, even1)]++;
if (mp.count(make_pair(has[i], has1[i])))has[i] = has1[i] = 0;
else mp[make_pair(has[i], has1[i])] = 1;
has_odd[i] = odd, has_even[i] = even;
has_odd1[i] = odd1, has_even1[i] = even1;
}
ans = 1ll * n * n;
for (int i = 1;i <= n;i++) {
if (!has[i])continue;
ans -= mp1[make_pair(has_odd[i], has_odd1[i])] * mp2[make_pair(has_even[i], has_even1[i])];
}
printf("%lld\n", ans);
return 0;
}
\(H. Garbage\)
也(?)是一道线段树。
处理区间 &、|、^用拆成位数棵树的方式(这也是个常用板子),关键在于如何处理每个数异或下标。
因为每个数要异或的下标是不变的,所以一开始就处理出某个数的某一位要异或 \(1\) 还是 \(0\)。整个过程中要维护的就是区间里要异或 \(1/0\) 的某一位此时为 \(1\) 的个数。
要注意的是,虽然保证了所有 \(a_i\) 和操作中的数值 \(x\) 都 \(<2^{15}\),但要异或的下标的范围是 \(n(\leq{10}^5)\),所以线段树要处理到 \(17\) 位。
H. Garbage
#include
#define ll long long
using namespace std;
const int N = 1e5 + 10;
int n, m, a[N][18], b[N][18];
int num[N << 2][18][2], cnt[N << 2][18][2], tag[N << 2][18], tmp[N << 2][18];
void pushup(int p, int pos) {
cnt[p][pos][0] = cnt[p << 1][pos][0] + cnt[p << 1 | 1][pos][0];
cnt[p][pos][1] = cnt[p << 1][pos][1] + cnt[p << 1 | 1][pos][1];
}
void pushdown(int p, int pos, int l, int r) {
if (tag[p][pos] != -1) {
tag[p << 1][pos] = tag[p][pos];
cnt[p << 1][pos][0] = num[p << 1][pos][0] * tag[p][pos];
cnt[p << 1][pos][1] = num[p << 1][pos][1] * tag[p][pos];
tag[p << 1 | 1][pos] = tag[p][pos];
cnt[p << 1 | 1][pos][0] = num[p << 1 | 1][pos][0] * tag[p][pos];
cnt[p << 1 | 1][pos][1] = num[p << 1 | 1][pos][1] * tag[p][pos];
tmp[p << 1][pos] = tmp[p << 1 | 1][pos] = 0;
tag[p][pos] = -1;
}
if (tmp[p][pos]) {
tmp[p << 1][pos] ^= 1, tmp[p << 1 | 1][pos] ^= 1;
cnt[p << 1][pos][0] = num[p << 1][pos][0] - cnt[p << 1][pos][0];
cnt[p << 1][pos][1] = num[p << 1][pos][1] - cnt[p << 1][pos][1];
cnt[p << 1 | 1][pos][0] = num[p << 1 | 1][pos][0] - cnt[p << 1 | 1][pos][0];
cnt[p << 1 | 1][pos][1] = num[p << 1 | 1][pos][1] - cnt[p << 1 | 1][pos][1];
tmp[p][pos] = 0;
}
}
void build(int p, int l, int r, int pos) {
tag[p][pos] = -1;
if (l == r) {
if (b[l][pos])num[p][pos][1] = 1, cnt[p][pos][1] = a[l][pos];
else num[p][pos][0] = 1, cnt[p][pos][0] = a[l][pos];
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid, pos), build(p << 1 | 1, mid + 1, r, pos);
pushup(p, pos);
num[p][pos][0] = num[p << 1][pos][0] + num[p << 1 | 1][pos][0];
num[p][pos][1] = num[p << 1][pos][1] + num[p << 1 | 1][pos][1];
}
void cover(int p, int l, int r, int L, int R, int pos, int y) {
if (L <= l && r <= R) {
cnt[p][pos][1] = num[p][pos][1] * y;
cnt[p][pos][0] = num[p][pos][0] * y;
tag[p][pos] = y;
tmp[p][pos] = 0;
return;
}
pushdown(p, pos, l, r);
int mid = (l + r) >> 1;
if (L <= mid)cover(p << 1, l, mid, L, R, pos, y);
if (R > mid)cover(p << 1 | 1, mid + 1, r, L, R, pos, y);
pushup(p, pos);
}
void change(int p, int l, int r, int L, int R, int pos) {
if (L <= l && r <= R) {
cnt[p][pos][1] = num[p][pos][1] - cnt[p][pos][1];
cnt[p][pos][0] = num[p][pos][0] - cnt[p][pos][0];
tmp[p][pos] ^= 1;
return;
}
pushdown(p, pos, l, r);
int mid = (l + r) >> 1;
if (L <= mid)change(p << 1, l, mid, L, R, pos);
if (R > mid)change(p << 1 | 1, mid + 1, r, L, R, pos);
pushup(p, pos);
}
ll ask(int p, int l, int r, int L, int R, int pos) {
if (L <= l && r <= R) {
return cnt[p][pos][0] + num[p][pos][1] - cnt[p][pos][1];
}
pushdown(p, pos, l, r);
ll val = 0;
int mid = (l + r) >> 1;
if (L <= mid)val += ask(p << 1, l, mid, L, R, pos);
if (R > mid)val += ask(p << 1 | 1, mid + 1, r, L, R, pos);
pushup(p, pos);
return val;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1;i <= n;i++) {
scanf("%d", &a[i][0]);
int x = a[i][0], cnt = 0;
while (x) {
if (x & 1)a[i][cnt] = 1;
else a[i][cnt] = 0;
cnt++;
x >>= 1;
}
x = i, cnt = 0;
while (x) {
if (x & 1)b[i][cnt] = 1;
else b[i][cnt] = 0;
cnt++;
x >>= 1;
}
}
for (int i = 0;i < 17;i++)build(1, 1, n, i);
char c;
int op, l, r, x;
while (m--) {
scanf("%d%d%d", &op, &l, &r);
if (op == 1) {
ll ans = 0, val = 1;
for (int i = 0;i < 17;i++) {
ans += ask(1, 1, n, l, r, i) * val;
val <<= 1;
}
printf("%lld\n", ans);
}
else if (op == 2) {
scanf("%d", &x);
for (int i = 0;i < 17;i++) {
if (x & 1)cover(1, 1, n, l, r, i, 1);
else cover(1, 1, n, l, r, i, 0);
x >>= 1;
}
}
else {
scanf("%d", &x);
cin >> c;
for (int i = 0;i < 17;i++) {
if (c == '&') {
if (!(x & 1))cover(1, 1, n, l, r, i, 0);
}
else if (c == '|') {
if (x & 1)cover(1, 1, n, l, r, i, 1);
}
else {
if (x & 1)change(1, 1, n, l, r, i);
}
x >>= 1;
}
}
}
return 0;
}
牛客第二场
\(G.Link\ with\ Centrally\ Symmetric\ Strings\)
该想到的关键点都有头绪了,但在赛场上没能有逻辑地组织起来,导致跟榜歪掉的 \(H\) 题也没能分到时间。
从前缀开始取完整的中心对称串,可以证明遇到最小的前缀中心对称串就把它分割掉是合法的。没搞懂题解的 \(manacher\) 修改版算法,用哈希处理了。
像 \(manacher\) 那样把原串处理成长为 \(n*2\),字符中间插入 \(*\) 的新串。从前往后遍历字符串,以每一个位置为中心判断它是不是一个当前串的前缀中心对称串。预处理时正反哈希各一次,判断是否为中心对称串的复杂度为 \(O(1)\)。如果判断当前位置为中心有前缀中心对称串,就把它裁掉。
取最小前缀中心对称串的正确性证明:
考虑一个中心对称串 \(S\),设其最短中心对称前缀为 \(A\),\(S\) 可视为 \(AB\)。若 \(A\) 的长度\(>\frac{|S|}{2}\),则 \(S\) 可划分为 \(B'CB\),其中 \(B'\) 和 \(B\) 中心对称,而 \(C\) 为一个中心对称串。因为 \(A\) 为一个中心对称前缀,所以其必含有一个与 \(C\) 中心对称且更短的的前缀 \(C'\),因此 \(A\) 不是最小的中心对称串前缀。
因此 \(S\) 可以被视为 \(ABA'\),\(A\)、\(B\)、\(A'\)都是中心对称串。对于原串分割成的每个好串 \(T\),如果其含有更短的中心对称前缀,则可以划分为三个更短的中心对称串,一直这样划分下去。于是依次取最短的中心对称前缀即可。
G.Link with Centrally Symmetric Strings
#include
#define ll long long
using namespace std;
const int N=2e6+10,mod=1e9+7,p=27,mod1=998244353,p1=31;
int t,n,a[N];
char s[N];
char c[1010];
ll has1[N],has2[N],tag[N];
ll has11[N],has21[N],tag1[N];
vector >v;
priority_queue >q;
inline ll Has1(int l,int r){
return (has1[r]-has1[l-1]*tag[r-l+1]%mod+mod)%mod;
}
inline ll Has11(int l,int r){
return (has11[r]-has11[l-1]*tag1[r-l+1]%mod1+mod1)%mod1;
}
inline ll Has2(int l,int r){
return (has2[l]-has2[r+1]*tag[r-l+1]%mod+mod)%mod;
}
inline ll Has21(int l,int r){
return (has21[l]-has21[r+1]*tag1[r-l+1]%mod1+mod1)%mod1;
}
inline bool check(int pos,int len){
if(pos+len-1>n||pos-len+1<0)return 0;
// printf("pos:%d len:%d 1:%lld 2:%lld\n",pos,len,Has1(pos-len+1,pos),Has2(pos,pos+len-1));
return Has1(pos-len+1,pos)==Has2(pos,pos+len-1)&&Has11(pos-len+1,pos)==Has21(pos,pos+len-1);
}
int main()
{
scanf("%d",&t);
c['b']='q',c['q']='b',c['d']='p',c['p']='d',c['n']='u',c['u']='n';
c['o']='o',c['s']='s',c['x']='x',c['z']='z',c['*']='*';
while(t--){
scanf("%s",s+1);
n=strlen(s+1);
s[0]='*';
for(int i=n;i>=1;i--){
s[(i<<1)-1]=s[i];
s[i<<1]='*';
}
n*=2;
has11[0]=has1[0]=s[0];
tag1[0]=tag[0]=1;
for(int i=1;i<=n;i++){
has1[i]=(has1[i-1]*p%mod+s[i])%mod;
has11[i]=(has11[i-1]*p1%mod1+s[i])%mod1;
tag[i]=tag[i-1]*p%mod;
tag1[i]=tag1[i-1]*p1%mod1;
}
for(int i=n;i>=1;i--){
has2[i]=(has2[i+1]*p%mod+c[s[i]])%mod;
has21[i]=(has21[i+1]*p1%mod1+c[s[i]])%mod1;
}
int l=1,r=1;
for(int i=1;i<=n;i++){
r=i;
int mid=(l+r)/2;
if(Has1(l,mid)==Has2(r-(mid-l),r)){
l=i+1;
if(l>n)break;
}
}
if(l>n)printf("Yes\n");
else printf("No\n");
}
return 0;
}
牛客第三场
$E.Koraidon, Miraidon\ and\ DFS\ Shortest\ Path $
没有见过的板子增加了orz赛场上真没搞出来
没有太弄明白支配树具体的原理,只知道用板子能求出有向图中每个点的最近支配点。在这道题里,首先 \(bfs\) 分层找到每个点实际上的最短距离,然后对于每条 \(x→y\) 的边,如果 \(d_x+1=d_y\) 那么没有影响,如果 \(d_x=d_y\) 则会有错误影响,如果 \(d_x+1>d_y\) 并且 \(x\) 不是 \(y\) 的支配点,则表示有一条路可以绕过 \(y\),通过 \(x\) 首次走到 \(y\),得到错误最短距离。
每个点的支配点可能会有多个,从自己的最近支配点一路跳到起点。因此建出支配树,从树根 \(dfs\) 下去,对于每个点在图上的原边再执行如上的判断。这样不需要暴力地找每个点的支配点,\(dfs\) 下来的路径就是当前点的支配点。
记一下板子
E.Koraidon, Miraidon and DFS Shortest Path
#include
using namespace std;
const int N = 5e5 + 10;
int t, n, m, anc[N], idom[N], d[N];
int dfn[N], rec[N], tim, fa[N], semi[N], best[N], vis[N];
int ver[N], tot, Next[N], head[N];
vectorsuf[N], pre[N], dom[N];
setans;
void add(int x, int y) {
suf[x].push_back(y);
pre[y].push_back(x);
}
void add1(int x, int y) {
ver[++tot] = y;
Next[tot] = head[x];
head[x] = tot;
}
void dfs(int x) {
dfn[x] = ++tim;
rec[tim] = x;
for (int i = 0;i < suf[x].size();i++) {
int y = suf[x][i];
if (!dfn[y]) {
dfs(y);
fa[dfn[y]] = dfn[x];
}
}
}
int find(int x) {
if (x == anc[x])return x;
int y = find(anc[x]);
if (semi[best[x]] > semi[best[anc[x]]]) {
best[x] = best[anc[x]];
}
return anc[x] = y;
}
void Lengauer_Tarjan() {
for (int y = tim;y > 1;y--) {
int x = fa[y];
for (int i = 0;i < pre[rec[y]].size();i++) {
int z = dfn[pre[rec[y]][i]];
if (!z)continue;
find(z);
semi[y] = min(semi[y], semi[best[z]]);
}
dom[semi[y]].push_back(y);
anc[y] = x;
for (int i = 0;i < dom[x].size();i++) {
int z = dom[x][i];
find(z);
idom[z] = (semi[best[z]] < x) ? best[z] : x;
}
dom[x].clear();
}
for (int i = 2;i <= tim;i++) {
if (idom[i] != semi[i])idom[i] = idom[idom[i]];
dom[idom[i]].push_back(i);
}
idom[1] = 0;
}
void bfs(int s) {
queueq;
q.push(s);
while (q.size()) {
int x = q.front();
q.pop();
for (int i = 0;i < suf[x].size();i++) {
int y = suf[x][i];
if (!d[y]) {
d[y] = d[x] + 1;
q.push(y);
}
}
}
}
int dfs1(int x) {
vis[x] = 1;
for (int i = 0;i < suf[x].size();i++) {
int y = suf[x][i];
if (d[y] == d[x])return 1;
if (d[y] <= d[x] - 1 && !vis[y])return 1;
}
for (int i = head[x];i;i = Next[i]) {
int y = ver[i];
if (dfs1(y))return 1;
}
vis[x] = 0;
return 0;
}
void init() {
for (int i = 1;i <= n;i++) {
suf[i].clear(), pre[i].clear(), dom[i].clear();
anc[i] = semi[i] = best[i] = i;
d[i] = dfn[i] = rec[i] = fa[i] = idom[i] = head[i] = vis[i] = 0;
}
tim = tot = 0;
}
int main()
{
// freopen("2.txt","r",stdin);
// freopen("my.txt","w",stdout);
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
init();
for (int i = 1, x, y;i <= m;i++) {
scanf("%d%d", &x, &y);
if (x == y)continue;
add(x, y);
}
dfs(1);
Lengauer_Tarjan();
// for(int i=1;i<=n;i++){
// printf("%d:%d\n",i,rec[idom[dfn[i]]]);
// }
d[1] = 1;
bfs(1);
// for(int i=1;i<=n;i++){
// printf("d %d:%d\n",i,d[i]);
// }
for (int i = 2;i <= n;i++)add1(rec[idom[dfn[i]]], i);
if (dfs1(1))printf("No\n");
else printf("Yes\n");
}
return 0;
}
洛谷的板子题,统计答案的时候可以建树也可以直接往上跳最近支配点,把点的答案逐层累计上去。
P5180 【模板】支配树
#include
using namespace std;
const int N = 3e5 + 10;
int n, m;
int dfn[N], rec[N], tim, fa[N], siz[N];
int semi[N], best[N], anc[N], idom[N];
int ver[N], tot, Next[N], head[N];
vectorpre[N], suf[N], dom[N];
void add_edge(int x, int y) {
ver[++tot] = y;
Next[tot] = head[x];
head[x] = tot;
}
void add(int x, int y) {
suf[x].push_back(y);
pre[y].push_back(x);
}
void dfs(int x) {
dfn[x] = ++tim;
rec[tim] = x;
for (int i = 0;i < suf[x].size();i++) {
int y = suf[x][i];
if (!dfn[y]) {
dfs(y);
fa[dfn[y]] = dfn[x];
}
}
}
int find(int x) {
if (x == anc[x])return x;
int y = find(anc[x]);
if (semi[best[x]] > semi[best[anc[x]]]) {
best[x] = best[anc[x]];
}
return anc[x] = y;
}
void Lengauer_Tarjan() {
for (int y = tim;y > 1;y--) {
int x = fa[y];
for (int i = 0;i < pre[rec[y]].size();i++) {
int z = dfn[pre[rec[y]][i]];
if (!z)continue;
find(z);
semi[y] = min(semi[y], semi[best[z]]);
}
dom[semi[y]].push_back(y);
anc[y] = x;
for (int i = 0;i < dom[x].size();i++) {
int z = dom[x][i];
find(z);
idom[z] = (semi[best[z]] < x) ? best[z] : x;
}
dom[x].clear();
}
for (int i = 2;i <= tim;i++) {
if (idom[i] != semi[i])idom[i] = idom[idom[i]];
dom[idom[i]].push_back(i);
}
idom[1] = 0;
}
void dfs_ans(int x) {
siz[x] = 1;
for (int i = head[x];i;i = Next[i]) {
int y = ver[i];
dfs_ans(y);
siz[x] += siz[y];
}
}
void init() {
for (int i = 1;i <= n;i++)anc[i] = semi[i] = best[i] = i;
}
int main()
{
scanf("%d%d", &n, &m);
init();
for (int i = 1, x, y;i <= m;i++) {
scanf("%d%d", &x, &y);
add(x, y);
}
dfs(1);
Lengauer_Tarjan();
// for(int i=2;i<=n;i++){
// add_edge(rec[idom[dfn[i]]],i);
// }
//dfs_ans(1);//建出树的做法
for (int i = n;i >= 1;i--)siz[rec[idom[dfn[i]]]] += ++siz[i];
//直接往上跳边的做法
for (int i = 1;i <= n;i++)printf("%d ", siz[i]);
return 0;
}
\(G.Beautiful\ Matrix\)
标程是行列哈希+二维manacher,另一种大部分人使用的解法是二维哈希+二分。
二维manacher的做法,因为判断是否匹配的过程实际上是一行一列向外扩展的,所以只需要行列分别哈希。二维manacher的过程没有太整明白,先当个板子记下来。
二分答案的做法,此时行列哈希就不足以判断了,所以从左上角和右下角开始分别处理一个二维哈希。注意行和列的进制数不能取相同值。(也可以当个板子记起来,比较方便)
因为本题卡常,所以二维哈希的做法用了inline优化过的。
G.Beautiful Matrix【二维manacher】
#include
#define ll long long
using namespace std;
const int N=4010,p=131,mod=998244353;
int f[N][N],n,m,posc[N];
char s[N][N];
ll hasr[N][N],hasr_r[N][N],hasc[N][N],hasc_r[N][N],len[N],ans;
void init(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
hasr[i][j]=(hasr[i][j-1]*p%mod+s[i][j])%mod;
}
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
hasc[i][j]=(hasc[i][j-1]*p%mod+s[j][i])%mod;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
hasr_r[i][j]=(hasr_r[i][j-1]*p%mod+s[i][m-j+1])%mod;
}
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
hasc_r[i][j]=(hasc_r[i][j-1]*p%mod+s[n-j+1][i])%mod;
}
}
len[0]=1;
for(int i=1;i<=max(n,m);i++){
len[i]=len[i-1]*p%mod;
}
}
ll subhash(int l,int r,ll *a){
return (a[r]+mod-a[l-1]*len[r-l+1]%mod)%mod;
}
bool cmp(int c1,int c2,int r1,int r2){
int rr1=m-r2+1,rr2=m-r1+1;
int rc1=n-c2+1,rc2=n-c1+1;
return subhash(r1,r2,hasr[c1])==subhash(rr1,rr2,hasr_r[c2])&&subhash(c1,c2,hasc[r1])==subhash(rc1,rc2,hasc_r[r2]);
}
void manacher(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int col=posc[j],row=j;
if(row+f[col][row]>=j){
f[i][j]=min(row+f[col][row]-j,col+f[col][row]-i);
if(col-(i-col)>0&&col-(i-col)<=n)f[i][j]=min(f[i][j],f[col-(i-col)][row-(j-row)]);
}
while(i-f[i][j]-1>0&&i+f[i][j]+1<=n&&j-f[i][j]-1>0&&j+f[i][j]+1<=m&&cmp(i-f[i][j]-1,i+f[i][j]+1,j-f[i][j]-1,j+f[i][j]+1)){
f[i][j]++;
}
if(i+f[i][j]>posc[j]+f[posc[j]][j])posc[j]=i;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
for(int i=n;i>=1;i--){
for(int j=m;j>=1;j--){
s[i*2+1][j*2+1]='#';
s[i*2+1][j*2]='#';
s[i*2][j*2+1]='#';
s[i*2][j*2]=s[i][j];
}
}
n=n<<1|1,m=m<<1|1;
for(int i=1;i<=n;i++)s[i][1]='#';
for(int i=1;i<=m;i++)s[1][i]='#';
init();
manacher();
for(int i=2;i<=n;i+=2){
for(int j=2;j<=m;j+=2){
ans+=(f[i][j]+1)>>1;
}
}
for(int i=3;i<=n;i+=2){
for(int j=3;j<=m;j+=2){
ans+=f[i][j]>>1;
}
}
printf("%lld\n",ans);
return 0;
}
G.Beautiful Matrix【二维哈希】
#include
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=2010,p=27,q=37,mod=998244353,p1=31,q1=41;
int n,m;
char s[N][N];
ll has[N][N],has_r[N][N],sum,len[N],lenr[N];
ull has1[N][N],has1_r[N][N],len1[N],lenr1[N];
inline ll check1(int u,int d,int l,int r){
return (has[d][r]-has[u-1][r]*lenr[d-u+1]%mod+mod-has[d][l-1]*len[r-l+1]%mod+has[u-1][l-1]*lenr[d-u+1]%mod*len[r-l+1]%mod+mod)%mod;
}
inline ll check2(int u,int d,int l,int r){
return (has_r[u][l]-has_r[d+1][l]*lenr[d-u+1]%mod+mod-has_r[u][r+1]*len[r-l+1]%mod+has_r[d+1][r+1]*lenr[d-u+1]%mod*len[r-l+1]%mod+mod)%mod;
}
inline ull check11(int u,int d,int l,int r){
return has1[d][r]-has1[u-1][r]*lenr1[d-u+1]-has1[d][l-1]*len1[r-l+1]+has1[u-1][l-1]*lenr1[d-u+1]*len1[r-l+1];
}
inline ull check21(int u,int d,int l,int r){
return has1_r[u][l]-has1_r[d+1][l]*lenr1[d-u+1]-has1_r[u][r+1]*len1[r-l+1]+has1_r[d+1][r+1]*lenr1[d-u+1]*len1[r-l+1];
}
int main()
{
// freopen("76.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
len[0]=len1[0]=lenr[0]=lenr1[0]=1;
for(int i=1;i<=max(n,m);i++)len[i]=len[i-1]*p%mod,len1[i]=len1[i-1]*p1,lenr[i]=lenr[i-1]*q%mod,lenr1[i]=lenr1[i-1]*q1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
has[i][j]=(has[i][j-1]*p%mod+s[i][j])%mod;
has_r[n-i+1][m-j+1]=(has_r[n-i+1][m-j+2]*p%mod+s[n-i+1][m-j+1])%mod;
has1[i][j]=has1[i][j-1]*p1+s[i][j];
has1_r[n-i+1][m-j+1]=has1_r[n-i+1][m-j+2]*p1+s[n-i+1][m-j+1];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
has[i][j]=(has[i-1][j]*q%mod+has[i][j])%mod;
has_r[n-i+1][m-j+1]=(has_r[n-i+2][m-j+1]*q%mod+has_r[n-i+1][m-j+1])%mod;
has1[i][j]=has1[i-1][j]*q1+has1[i][j];
has1_r[n-i+1][m-j+1]=has1_r[n-i+2][m-j+1]*q1+has1_r[n-i+1][m-j+1];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int l=2,r=min({j,i,n-i+1,m-j+1}),ans=1;
while(l<=r){
int mid=(l+r)>>1;
if(check1(i-mid+1,i+mid-1,j-mid+1,j+mid-1)==check2(i-mid+1,i+mid-1,j-mid+1,j+mid-1)&&check11(i-mid+1,i+mid-1,j-mid+1,j+mid-1)==check21(i-mid+1,i+mid-1,j-mid+1,j+mid-1)){
ans=mid;
l=mid+1;
}
else r=mid-1;
}
sum+=ans;
l=1,r=min({j,i,n-i,m-j}),ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(check1(i-mid+1,i+mid,j-mid+1,j+mid)==check2(i-mid+1,i+mid,j-mid+1,j+mid)&&check11(i-mid+1,i+mid,j-mid+1,j+mid)==check21(i-mid+1,i+mid,j-mid+1,j+mid)){
ans=mid;
l=mid+1;
}
else r=mid-1;
}
// printf("i:%d j:%d %d\n",i,j,ans);
sum+=ans;
}
}
printf("%lld\n",sum);
return 0;
}
杭电第三场
\(1009.Operation\ Hope\)
队友补了2-sat,我补了贪心。
把三种属性都存进优先队列,每次选极差最大的改掉。
1009.Operation Hope
#include
#define ll long long
using namespace std;
const int N = 1e5 + 10;
int t, n, aa[2][N], bb[2][N], cc[2][N], tag[N], cnt;
ll ans;
priority_queue, int> >a, b, c, A, B, C;
int main()
{
// freopen("1009.txt","r",stdin);
// freopen("my.txt","w",stdout);
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
cnt = 0;
ans = 1e18;
while (a.size())a.pop();
while (b.size())b.pop();
while (c.size())c.pop();
while (A.size())A.pop();
while (B.size())B.pop();
while (C.size())C.pop();
for (int i = 1;i <= n;i++) {
tag[i] = 0;
scanf("%d%d%d", &aa[0][i], &bb[0][i], &cc[0][i]);
scanf("%d%d%d", &aa[1][i], &bb[1][i], &cc[1][i]);
a.push(make_pair(make_pair(-aa[0][i], 0), i));
b.push(make_pair(make_pair(-bb[0][i], 0), i));
c.push(make_pair(make_pair(-cc[0][i], 0), i));
A.push(make_pair(make_pair(aa[0][i], 0), i));
B.push(make_pair(make_pair(bb[0][i], 0), i));
C.push(make_pair(make_pair(cc[0][i], 0), i));
}
while (cnt <= n * 2) {
int x;
while (a.size() && a.top().first.second != tag[a.top().second])a.pop();
while (b.size() && b.top().first.second != tag[b.top().second])b.pop();
while (c.size() && c.top().first.second != tag[c.top().second])c.pop();
while (A.size() && A.top().first.second != tag[A.top().second])A.pop();
while (B.size() && B.top().first.second != tag[B.top().second])B.pop();
while (C.size() && C.top().first.second != tag[C.top().second])C.pop();
ll vala = A.top().first.first + a.top().first.first;
ll valb = B.top().first.first + b.top().first.first;
ll valc = C.top().first.first + c.top().first.first;
// printf("vala:%lld valb:%lld valc:%lld\n",vala,valb,valc);
if (vala >= valb && vala >= valc) {
ans = min(ans, vala);
x = a.top().second;
a.pop();
}
else if (valb >= vala && valb >= valc) {
ans = min(ans, valb);
x = b.top().second;
b.pop();
}
else {
ans = min(ans, valc);
x = c.top().second;
c.pop();
}
tag[x] ^= 1;
a.push(make_pair(make_pair(-aa[tag[x]][x], tag[x]), x));
A.push(make_pair(make_pair(aa[tag[x]][x], tag[x]), x));
b.push(make_pair(make_pair(-bb[tag[x]][x], tag[x]), x));
B.push(make_pair(make_pair(bb[tag[x]][x], tag[x]), x));
c.push(make_pair(make_pair(-cc[tag[x]][x], tag[x]), x));
C.push(make_pair(make_pair(cc[tag[x]][x], tag[x]), x));
cnt++;
}
printf("%lld\n", ans);
}
return 0;
}
杭电第四场
\(1009.WO\ MEI\ K\)
对于每条路只需要选出两个点的情况,问题转化成统计每条边的贡献。对于树上的每条边 \(x-y\),若 \(y\) 是 \(x\) 的儿子,那么两端点一个能放置的范围在 \(y\) 子树内,另一个在除 \(y\) 这棵子树以外的其他点中。设每个点原本的子树内可选点数量为 \({ans}_x\),初始为子树大小 \({siz}_x\)。考虑边权的限制,记下每个点最近的不能再继续向上选的祖先 \({top}_x\),令该祖先的子树内可选点数量 \({ans}_{{top}_x}\) 减去自己的子树大小 \({siz}_x\)。那么对于该边 \(x-y\) 而言,贡献就是 \({ans}_y*{ans}_{{top}_y}\)。
对于 \(k>2\) 的情况,等价于每条路径上除了这两个点以外其他的点任意选取,答案再乘上 \(\tbinom{n-2}{k-2}\)。
1009.WO MEI K
#include
#define ll long long
using namespace std;
const int N = 4e5 + 10, mod = 998244353;
int t, n, top[N], siz[N], ans[N];
int ver[N << 1], head[N], tot, Next[N << 1], edge[N << 1];
ll res, f[N], inv[N], tmp;
vectorv[N];
ll pw(ll x, ll p) {
ll num = 1;
while (p) {
if (p & 1)num = num * x % mod;
x = x * x % mod;
p >>= 1;
}
return num;
}
void add(int x, int y, int z) {
ver[++tot] = y;
Next[tot] = head[x];
head[x] = tot;
edge[tot] = z;
}
void dfs(int x, int fa) {
siz[x] = 1;
for (int i = head[x];i;i = Next[i]) {
int y = ver[i], z = edge[i];
if (y == fa)continue;
if (v[z].size())top[y] = v[z].back();
else top[y] = n + z;
v[z].push_back(y);
dfs(y, x);
v[z].pop_back();
siz[x] += siz[y];
ans[top[y]] -= siz[y];
}
ans[x] += siz[x];
}
int main()
{
scanf("%d", &t);
f[0] = f[1] = inv[0] = 1;
for (int i = 2;i <= N - 10;i++)f[i] = f[i - 1] * i % mod;
inv[N - 10] = pw(f[N - 10], mod - 2);
for (int i = N - 10 - 1;i >= 1;i--)inv[i] = inv[i + 1] * (i + 1) % mod;
while (t--) {
scanf("%d", &n);
tot = res = tmp = 0;
for (int i = 1;i <= n;i++)head[i] = ans[i] = 0;
for (int i = n + 1;i <= 2 * n;i++)ans[i] = n;
for (int i = 1, x, y, z;i < n;i++) {
scanf("%d%d%d", &x, &y, &z);
add(x, y, z), add(y, x, z);
}
dfs(1, 0);
for (int i = 2;i <= n;i++) {
res = (res + 1ll * ans[i] * ans[top[i]] % mod) % mod;
// printf("%d %d %d %d\n",i,top[i],ans[i],ans[top[i]]);
}
for (int i = 2;i <= n;i++) {
tmp ^= (res * f[n - 2] % mod * inv[i - 2] % mod * inv[n] % mod * f[i] % mod);
}
printf("%lld\n", tmp);
}
return 0;
}
牛客第五场
\(C.Cheeeeen\ the\ Cute\ Cat\)
比赛的时候队友猜结论过了,赛后数据加强卡掉了错解,从赛场通过数来看错解率还挺高的:9
正解是竞赛图的性质。首先因为原图若 \(i \leftrightarrow j+n\) 的边存在则不会有 \(j \leftrightarrow i+n\) 的边存在,所以可以把 \(i \leftrightarrow j+n\) 视为一条 \(i→j\) 的有向边。竞赛图为在无向完全图中为每个边缘分配方向而获得的有向图,本题转化连边方式后就成为了 \(n\) 个点连通的竞赛图。
竞赛图有一些很好的性质,比如一定存在一条哈密顿通路,这使得这题的最小答案一定是 \(n-1\),哈密顿通路上的每一条边都是满足条件的一组匹配。
答案为 \(n\) 的情况,所有点一定在至少一个强连通分量中,如何判断这一情况需要用到兰道定理。
兰道定理:对于一个出度序列 \(s1…n\),它是合法的(存在一个竞赛图出度满足这个序列),当且仅当:
且在 \(k=n\) 时取等。出度可以换成入度。
而兰道定理中的不等式取等时,代表目前前面的所有点组成了一个强连通分量。所有使得不等式取等的 \(k\) 都是强连通分量的分界点,只要判断所有强连通分量的大小都至少为 \(2\),就表示图中所有点都在环上,答案为 \(n\)。
(参考博客:CHiSwsz)
C.Cheeeeen the Cute Cat
#include
using namespace std;
const int N=3e3+10;
int n,s[N],sum,ans;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1,x;j<=n;j++){
scanf("%d",&x);
if(x)s[i]+=x;
}
}
sort(s+1,s+n+1);
for(int i=1,lst=0;i<=n;i++){
sum+=s[i];
if(sum==i*(i-1)/2){
if(i-lst<2){
printf("%d\n",n-1);
return 0;
}
lst=i;
}
}
printf("%d\n",n);
return 0;
}
\(E.Red\ and\ Blue\ and\ Green\)
比赛的时候没找到哪错了,队友重构过了
赛后发现是排序的时候,若位置相等但不是同一种端点,此时可能是长为1的小区间的情况,应该让左端点在前。
区间要么包含要么不相交,可以建树——一种常用的思路,记一下。
E.Red and Blue and Green
#include
using namespace std;
const int N = 1e3 + 10;
int n, m, cnt, st[N << 1], top, L[N], R[N], W[N], flag, a[N], maxx[N], minn[N];
vectorv[N];
struct node {
int op, l, r, w, id, pos;
}b[N << 2];
bool cmp(node x, node y) {
if (x.pos == y.pos) {
if (x.op == y.op) {
if (x.op == 0) {
return x.r == y.r ? x.wy.r;
}
else {
return x.l == y.l ? x.w > y.w:x.l > y.l;
}
}
return x.op < y.op;
}
return x.pos < y.pos;
}
int dfs(int x) {
int l = L[x], r = R[x], w = W[x], op = 0, odd = 0, even = 0;
int lst = l - 1;
for (int i = 0;i < (int)v[x].size();i++) {
int y = v[x][i];
if (dfs(y) == 1)op ^= 1;
}
// printf("x:%d siz:%d w:%d op:%d\n",x,(int)v[x].size(),w,op);
if (op == (w ^ 1)) {
if ((int)v[x].size() > 1) {
swap(a[maxx[v[x][0]]], a[minn[v[x][1]]]);
}
else if ((int)v[x].size() == 1) {
int y = v[x][0];
if (L[y] > L[x])swap(a[minn[y]], a[L[x]]);
else if (R[y] < R[x])swap(a[R[x]], a[maxx[y]]);
else flag = 1;
}
else {
if (r > l)swap(a[l], a[l + 1]);
else flag = 1;
}
}
int ma = 0, mi = n + 1;
for (int i = L[x];i <= R[x];i++) {
// printf("%d ",a[i]);
if (a[i] > ma) {
ma = a[i];
maxx[x] = i;
}
if (a[i] < mi) {
mi = a[i];
minn[x] = i;
}
}
// printf("\n");
return w;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1;i <= n;i++)a[i] = i;
for (int i = 1, l, r, w;i <= m;i++) {
scanf("%d%d%d", &l, &r, &w);
L[i] = l, R[i] = r, W[i] = w;
b[++cnt].l = l, b[cnt].r = r, b[cnt].w = w, b[cnt].op = 0, b[cnt].id = i, b[cnt].pos = l;
b[++cnt].l = l, b[cnt].r = r, b[cnt].w = w, b[cnt].op = 1, b[cnt].id = i, b[cnt].pos = r;
}
sort(b + 1, b + cnt + 1, cmp);
// for(int i=1;i<=cnt;i++){
// printf("l:%d r:%d w:%d op:%d id:%d pos:%d\n",b[i].l,b[i].r,b[i].w,b[i].op,b[i].id,b[i].pos);
// }
for (int i = 1;i <= cnt;i++) {
if (b[i].op == 1) {
top--;
v[b[st[top]].id].push_back(b[i].id);
// printf("top:%d %d %d\n",b[st[top]].id,b[i].id);
}
else {
st[++top] = i;
}
// printf("i:%d top:%d\n",i,top);
}
L[0] = 1, R[0] = n, W[0] = 2;
dfs(0);
// flag=0;
if (flag)printf("-1\n");
else {
for (int i = 1;i <= n;i++) {
printf("%d ", a[i]);
}
printf("\n");
}
return 0;
}
\(I.The\ Yakumo\ Family\)
首先拆位计算贡献,这个也属于位运算题的常用思路。
假设枚举的是中间的区间,那么先处理出每个位置作为左/右端点时左边/右边那个区间的全部贡献 \(L_i\) 和 \(R_i\)。利用前缀异或和,在某个位置的时候有贡献的区间就是差分异或一下为 \(1\) 的区间,也就是 \(c_0*c_1\)。
然后枚举中间区间的右端点,对于当前的右端点 \(r\) 来说,右边的答案可以乘上去,而左边要像之前一样处理一个贡献和。每次左端点产生贡献的仍然是前缀异或和与右端点取反的,这样中间的区间异或出来的结果为 \(1\)。
I.The Yakumo Family
#include
#define ll long long
using namespace std;
const int N=2e5+10,mod=998244353;
int n,a[N];
ll L[N],pre[N],R[N],sum[N],ans,num[2],s[2];
int main()
{
scanf("%d",&n);
pre[0]=1;
for(int i=1;i<=30;i++){
pre[i]=pre[i-1]*2%mod;
}
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum[i]=sum[i-1]^a[i];
}
for(int k=0;k<=30;k++){
num[0]=num[1]=0;
for(int i=0;i<=n;i++){
num[(sum[i]>>k)&1]++;
L[i]=(L[i]+num[1]*num[0]%mod*pre[k]%mod)%mod;
}
num[0]=num[1]=0;
for(int i=n;i>=0;i--){
num[(sum[i]>>k&1)]++;
R[i]=(R[i]+num[1]*num[0]%mod*pre[k]%mod)%mod;
}
}
for(int k=0;k<=30;k++){
s[0]=s[1]=0;
for(int r=1;r<=n;r++){
int now=(sum[r]>>k)&1;
ans=(ans+s[now^1]*R[r]%mod*pre[k]%mod)%mod;
s[now]=(s[now]+L[r])%mod;
}
}
printf("%lld\n",ans);
return 0;
}
杭电第五场
\(1003.String\ Magic\ (Easy\ Version)\)
直接用类似manacher的方法暴力统计以某个位置为回文中心的区间内,有多少个回文中心的半径能超过当前位置,即当前位置的回文半径中某一边有覆盖或接触到回文中心的小回文串,那么另一边一定也存在相对应的回文串。
要注意的是,小回文串的回文中心只要枚举到大回文串回文半径的一半就好了,因为距离超过大回文半径一半的回文中心,若它的回文区间碰到或者覆盖了大回文区间的中心,那么该小回文区间一定包含大回文半径之外的字符,不满足条件。
这种统计方式有点类似于manacher算法本身处理的过程,跑得飞快。
String Magic (Easy Version)
#include
#define ll long long
using namespace std;
const int N=2e5+10;
int t,a[N<<1],n;
ll tree[N<<1],res[N<<1],ans;
char s[N<<1];
void manacher(){
int pos=0,r=0;
a[0]=1;
for(int i=1;i<=n;i++){
if(pos+r-1=0)len++;
a[i]=len+1;
r=a[i];
}
else{
int j=(pos<<1)-i;
if(i+a[j]-1pos+r-1)a[i]=pos+r-i;
else{
a[i]=a[j];
while(s[i+a[i]]==s[i-a[i]]&&i+a[i]<=n&&i-a[i]>=0)a[i]++;
pos=i;
r=a[i];
}
}
}
for(int i=2;i<=n;i+=2){
if(a[i]>1){
for(int j=1;j<=a[i]/2;j++){
if(a[i+j]>=j)ans++;
}
// printf("i:%d ans:%d\n",i,ans);
}
}
}
int main()
{
scanf("%d",&t);
while(t--){
ans=0;
scanf("%s",s+1);
n=strlen(s+1);
s[0]='#';
for(int i=n;i>=1;i--){
s[i<<1]='#';
s[(i<<1)-1]=s[i];
}
n<<=1;
manacher();
printf("%lld\n",ans);
}
return 0;
}
\(1010.Cut\ The\ Tree\)
题意转化后为:对于树上的每条边,断掉该边后在两棵新树中分别取两异或值最大点,将两组最大异或值相减后得到 \(val_i\)。要求出的就是所有 \(val\) 中最小的那一个。
统计子树内的最大异或值可以用 \(dsu on tree+01trie\),要注意的是最大异或值为子树内任取两点得出,而不是当前节点和子树内选一点求得,所以在启发式合并加入轻儿子节点的过程中就要不断更新最大值且顺便记下全局最大值和得出最大值的两点。
然后考虑如何求出子树外的最大异或值,对于刚刚记下的得出全局最大异或值的两点,容易发现除了根分别到这两点的路径上的点以外,其他所有点的子树外最大异或值都是这两点异或得到的全局最大异或值。那么在从根走到这两点的过程中,把所有除包含这两点的子树以外的子树全部加入 \(01trie\),再统计答案就可以了。
注意加点和求答案的顺序。
Cut The Tree
#include
using namespace std;
const int N=2e5+5;
int t,n,a[N],siz[N],son[N],f[N],g[N],tree[N*30][2],cnt,b[N*30],ans,id[N*30],pos,c[N][31],res;
int maxx,X,Y,Fa[N],vis[N];
int ver[N<<1],head[N],tot,Next[N<<1];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
inline void add(int x,int y){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
}
void dfs(int x,int fa){
siz[x]=1;
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(y==fa)continue;
Fa[y]=x;
dfs(y,x);
if(siz[y]>siz[son[x]])son[x]=y;
siz[x]+=siz[y];
}
}
inline void insert(int v,int op){
int now=0;
for(int i=30;i>=0;i--){
if(!tree[now][c[v][i]]){
tree[now][c[v][i]]=++cnt;
tree[cnt][0]=tree[cnt][1]=0;
b[cnt]=id[cnt]=0;
}
now=tree[now][c[v][i]];
b[now]+=op;
}
if(op==1)id[now]=v;
}
inline int query(int x){
int now=0,val=0;
for(int i=30;i>=0;i--){
int y=c[x][i];
if(b[tree[now][y^1]])now=tree[now][y^1],val=(val<<1)+1;
else if(b[tree[now][y]])now=tree[now][y],val=(val<<1);
else return 0;
// if(x==1){
// printf("i:%d y:%d val:%d b:%d\n",i,y,val,b[now]);
// }
}
if(b[now])pos=id[now];
else pos=0;
return val;
}
inline void init(int x,int fa,int op){
if(op==1)res=max(res,query(x));
if(res>maxx){
maxx=res,X=x,Y=pos;
}
// if(query(x)==125){
// printf("x:%d pos:%d\n",x,pos);
// }
insert(x,op);
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(y==fa)continue;
init(y,x,op);
}
}
void dfs1(int x,int fa){
// if(t==3)printf("%d %d\n",x,fa);
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(y==fa||y==son[x])continue;
dfs1(y,x);
init(y,x,-1);
}
if(son[x])dfs1(son[x],x);
res=0;
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(y==fa||y==son[x])continue;
f[x]=max(f[x],f[y]);
init(y,x,1);
}
f[x]=query(x);
if(f[x]>maxx)maxx=f[x],X=x,Y=pos;
// printf("x:%d X:%d Y:%d max:%d\n",x,X,Y,maxx);
f[x]=max(f[x],f[son[x]]);
insert(x,1);
f[x]=max(f[x],res);
// printf("x:%d res:%d\n",x,res);
}
void dfs2(int x,int lst){
vis[x]=1;
// printf("x:%d lst:%d\n",x,lst);
if(Fa[x])dfs2(Fa[x],x);
g[x]=max(g[x],res);
g[x]=max(g[Fa[x]],g[x]);
// printf("x:%d B:%d\n",x,b[1]);
// printf("x:%d query:%d g:%d\n",x,query(x),g[x]);
insert(x,1);
res=max(res,query(x));
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(y==Fa[x]||y==lst)continue;
init(y,x,1);
}
// printf("x:%d query:%d g:%d\n",x,query(x),g[x]);
}
void clear(){
ans=1e9;
cnt=tot=maxx=pos=0;
for(int i=1;i<=n;i++)head[i]=son[i]=f[i]=g[i]=vis[i]=Fa[i]=0;
// memset(tree,0,sizeof(tree));
// memset(b,0,sizeof(b));
// memset(id,0,sizeof(id));
}
int main()
{
// freopen("1010.in","r",stdin);
// int size(256<<20);
// __asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
t=read();
while(t--){
n=read();
clear();
for(int i=1;i<=n;i++){
a[i]=read();
int x=a[i];
for(int j=0;j<=30;j++){
c[i][j]=(x&1);
x>>=1;
}
}
for(int i=1,x,y;i1&&n-siz[i]>1)ans=min(ans,abs(g[i]-f[i]));
else ans=min(ans,max(g[i],f[i]));
// printf("i:%d f:%d g:%d\n",i,f[i],g[i]);
}
printf("%d\n",ans);
}
return 0;
}
\(1011.Cactus\ Circuit\)
如果一条边不在环上,那么它断了整个系统都会断,所以首先可以对这种桥边取 \(min\)。
对于在环上的边,会对答案产生影响的是环上前三小的边。最小的两条边可以互相代替以保证环上其他点连通,而第三小的边不论最小的两条连哪个都始终要连着,所以也会限制答案。
用 \(tarjan\) 在过程中处理环上的边。
Cactus Circuit
#include
using namespace std;
const int N=2e5+10,inf=2e9;
int t,n,m,ans;
int ver[N<<1],head[N],tot,Next[N<<1],edge[N<<1],T[N],cnt;
int low[N],dfn[N],dfc,e[N],tp,stk[N],tmp[N<<1];
queueq;
void add(int x,int y,int z){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
}
void tarjan(int u,int z){
low[u]=dfn[u]=++dfc;
for(int i=head[u];i;i=Next[i]){
int v=ver[i];
if(tmp[i])continue;
if(!dfn[v]){
stk[++tp]=i;e[tp]=edge[i];
tmp[i]=tmp[i^1]=1;
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
cnt=0;
// printf("u:%d\n",u);
int x;
do{
x=stk[tp];
// printf("%d ",e[tp]);
T[++cnt]=e[tp--];
}while(x!=i);
// printf("\n");
// T[++cnt]=edge[i];
sort(T+1,T+cnt+1);
if(cnt==1)ans=min(ans,T[1]);
else if(cnt>2)ans=min({ans,T[1]+T[2],T[3]});
else ans=min(ans,T[1]+T[2]);
// printf("x:%d %d %d %d\n",u,T[1],T[2],T[3]);
}
}
else{
if(dfn[u]>dfn[v]&&i!=(z^1))stk[++tp]=i,e[tp]=edge[i];
low[u]=min(low[u],dfn[v]);
}
}
}
void init(){
ans=inf;
tot=1;
dfc=0;
for(int i=1;i<=n;i++)head[i]=dfn[i]=0;
for(int i=1;i<=m*2;i++)tmp[i]=0;
}
int main()
{
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
init();
for(int i=1,x,y,z;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z),add(y,x,z);
}
tarjan(1,0);
printf("%d\n",ans);
}
return 0;
}