【前言】
很久没有时间整理题解了,补题和打游戏的时间居多(doge)
这场其实主要F出题人数据有锅,花太多时间了(赛后重测是一血),然后后面G想歪了爆搜剪枝没过,I的回滚莫队队友前一天写了结果今天写不出来。
不过还行,rk38,校2/9
A.Away from College
【题目】
n n n个点 m m m条边的仙人掌,有 q q q个询问形如 ( c i , d i , l i ) (c_i,d_i,l_i) (ci,di,li),表示询问一个点 x i x_i xi,满足它在 c i c_i ci到 l i l_i li的最短路上,且在 c i c_i ci到 d i d_i di的最短路上,有多个答案,优先输出离 c i c_i ci最远的,有多个再输出编号最小的
n , q ≤ 1 0 5 , m ≤ 3 2 ( n − 1 ) n,q\leq 10^5,m\leq \frac 3 2(n-1) n,q≤105,m≤23(n−1)
【思路】
首先把圆方树建出来,然后分别考虑求 x i x_i xi 和求 d i s i dis_i disi。
求 x i x_i xi 的话首先把 L C A ( c i , d i ) LCA(c_i, d_i) LCA(ci,di),$ LCA(c_i, l_i)$, L C A ( d i , l i ) LCA(d_i, l_i) LCA(di,li) 都求出来,然后根据这三个点的位置关系进行分类讨论,这里就先介绍其中的一种情况:如果$ LCA(d_i, l_i)$ 在 c i c_i ci 的子树中,此时如果$ LCA(d_i, l_i)$ 是原始点那么 x i = L C A ( d i , l i ) x_i = LCA(d_i, l_i) xi=LCA(di,li),否则可以把问题规约成 c i , d i , l i c_i, d_i, l_i ci,di,li 在同一个环中的情况,这个比较容易可以讨论出来。别的情况也是类似的处理。
求 d i s i dis_i disi 的话,可以把每个真实点的权值标成这个点走到这个点双对应的根节点的步数,然后倍增即可。注意特殊处理一下最短路经过 L C A ( c i , x i ) LCA(c_i, x_i) LCA(ci,xi) 所在的点双的长度。
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
const int maxn=3e5+5;
struct edge{
int to,nxt,w;
}e[maxn*4];
int h[maxn],nh[maxn];
int tot=1;
void addedge(int x,int y){
e[++tot]=(edge){y,h[x],1};h[x]=tot;
}
void addedge2(int x,int y,int w){
e[++tot]=(edge){y,nh[x],w};nh[x]=tot;
}
int dfn[maxn],low[maxn],sta[maxn],top_=0,dfs_time;
int d[maxn],cir[maxn];
int cc=0;
int n,m,q;
void tarjan(int x,int pre){
dfn[x]=low[x]=++dfs_time;
sta[++top_]=x;
for(int i=h[x];i;i=e[i].nxt){
int y=e[i].to;
if(!dfn[y]){
d[y]=d[x]+1;
tarjan(y,i);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]){
addedge2(x,y,1); top_--;
}
else if(low[y]==dfn[x]){
int o=(++cc)+n;
cir[o]=d[sta[top_]]-d[x]+1;
addedge2(x,o,0);
for(int u=0;u!=y;){
u=sta[top_--];
addedge2(o,u,min(d[u]-d[x],cir[o]-(d[u]-d[x])));
}
}
}
else if(i!=(pre^1)){
low[x]=min(low[x],dfn[y]);
}
}
}
int fa[maxn],son[maxn],siz[maxn],dep[maxn],dis[maxn];
int top[maxn],L[maxn],R[maxn],dtime;
void dfs1(int x){
siz[x]=1;
for(int i=nh[x];i;i=e[i].nxt)if(e[i].to!=fa[x]){
int y=e[i].to;
fa[y]=x;
dep[y]=dep[x]+1;
dis[y]=dis[x]+e[i].w;
dfs1(y);
siz[x]+=siz[y];
if(siz[y]>siz[son[x]]) son[x]=y;
}
}
void dfs2(int x,int t){
top[x]=t;
L[x]=++dtime;
if(son[x]) dfs2(son[x],t);
for(int i=nh[x];i;i=e[i].nxt)if(e[i].to!=fa[x]&&e[i].to!=son[x]){
dfs2(e[i].to,e[i].to);
}
R[x]=dtime;
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]>dep[top[y]]) x=fa[top[x]];
else y=fa[top[y]];
}
return dep[x]<dep[y]?x:y;
}
bool is(int a,int x){
return L[a]<=L[x]&&R[a]>=R[x];
}
int child(int a,int x){
if(is(son[a],x)) return son[a];
while(fa[top[x]]!=a){
x=fa[top[x]];
}
return top[x];
}
int dst(int x,int y){
int l=lca(x,y);
if(l<=n) return dis[x]+dis[y]-(dis[l]<<1);
else{
int x1=child(l,x),y1=child(l,y);
int delta=abs(d[x1]-d[y1]);
return dis[x]-dis[x1]+dis[y]-dis[y1]+min(delta,cir[l]-delta);
}
}
pii check(int c,int a,int b,int x){
int D=dst(x,c);
return (D+dst(x,a)==dst(c,a)&&D+dst(x,b)==dst(c,b))? mkp(D,x):mkp(0,c);
}
int main(){
// freopen("my.in","r",stdin);
n=yh(),m=yh(),q=yh();
rep(i,1,m){
int x=yh(),y=yh();
addedge(x,y);addedge(y,x);
}
tarjan(1,0);
dfs1(1);dfs2(1,1);
rep(i,1,q){
int c=yh(),a=yh(),b=yh();
int l=lca(c,a)^lca(c,b)^lca(a,b);
if(l<=n){
cout<<l<<" "<<dst(l,c)<<hvie;
continue;
}
pii ans(0,c);
if(fa[l])ans=max(ans,check(c,a,b,fa[l]));
if(is(l,c)) ans=max(ans,check(c,a,b,child(l,c)));
if(is(l,b)) ans=max(ans,check(c,a,b,child(l,b)));
if(is(l,a)) ans=max(ans,check(c,a,b,child(l,a)));
cout<<ans.se<<" "<<ans.fi<<hvie;
}
return 0;
}
B. Boxes
【题目】
有 n n n个盒子,每个里面的球是黑白的概率都是 1 2 \frac 1 2 21。现在知道一个球是黑还是白分别需要花费 w i w_i wi,知道一共有多少个黑多少个白需要花费 C C C,问知道所有的期望最小花费。
n ≤ 1 0 5 n\leq 10^5 n≤105
【思路】
首先 C C C最多只用一次,而且可以在开始用,所以有两种策略。
- 全部打开,花费 ∑ w i \sum w_i ∑wi
- 将 w i w_i wi升序排序,先花费 C C C,剩下就是相当于一个随机01序列依次打开,开到一个后缀全是同色的为止,那么期望代价就是: C + ∑ w i ( 1 − 1 2 n − i ) C+\sum w_i(1-\frac 1 {2^{n-i}}) C+∑wi(1−2n−i1)
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int maxn=3e5+5;
int n,m;
db w[maxn],s[maxn];
int main(){
cin>>n;
double c;cin>>c;
rep(i,1,n){
cin>>w[i];
}
sort(w+1,w+1+n);
rep(i,1,n) s[i]=s[i-1]+w[i];
db ans=c,pw=0.5;
dwn(i,n-1,0){
ans+=pw*s[i];
// cout<<s[i]<<" "<<pw<<" "<<ans<<hvie;
pw*=0.5;
}
cout<<fixed<<setprecision(12)<<min(ans,s[n])<<hvie;
return 0;
}
C. Cheating and Stealing
【题目】
两个人打乒乓球,打了 n n n个小分, ∀ k ∈ [ 1 , n ] \forall k\in[1,n] ∀k∈[1,n],求出当一局得 k k k分获胜,且满足“赢两分”的限制时,第一个人赢的局数 f k f_k fk
n ≤ 1 0 6 n\leq 10^6 n≤106
【思路】
首先,当一局得分是 k k k的时候,最多会有 O ( n k ) O(\frac n k) O(kn)局,那么事实上总的局数是 O ( n log n ) O(n\log n) O(nlogn)的,也就是我们只要能 O ( 1 ) O(1) O(1)处理每一局就能解决这个问题。
我们首先考虑一个人得分是 k k k这个问题,我们可以通过二分来解决这个问题,但这样会多一个 log \log log(事实上比赛的时候我写的二分,$2\log 过 去 了 ) 。 一 种 更 优 秀 的 处 理 方 式 是 预 处 理 出 过去了)。一种更优秀的处理方式是预处理出 过去了)。一种更优秀的处理方式是预处理出posw[i] 和 和 和posl[i] 分 别 表 示 第 分别表示第 分别表示第i 个 个 个w/l$的位置,那这样我们就可以快速找到对应位置。
接下来要考虑”赢两分“的限制,不难发现这个限制被打破一定是一堆 w l w l wlwl wlwl交错出现以后有两个连续一样的字符。那么我们对于每个位置,处理出 g i g_i gi表示这个位置以后打破平局的位置,即可。
然后就是一些细节处理了,复杂度 O ( n log n ) O(n\log n) O(nlogn)
【参考代码】
赛时$2\log $
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=1e6+10,mod=998244353;
int n;
int suml[N],sumw[N],sum[N],rig[N],f[N];
char s[N];
int read()
{
int ret=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return f?ret:-ret;
}
int upm(int x){return x>=mod?x-mod:(x<0?x+mod:x);}
void up(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
void up(ll &x,ll y){x=(x+y>=mod?x+y-mod:x+y);}
int mul(int x,int y){return 1ll*x*y%mod;}
int main()
{
n=read();scanf("%s",s+1);
for(int i=1;i<=n;++i)
{
suml[i]=suml[i-1]+(s[i]=='L');
sumw[i]=sumw[i-1]+(s[i]=='W');
sum[i]=sum[i-1]+(s[i]=='W'?1:-1);
}
rig[n]=rig[n+1]=n+1;
for(int i=n-1;i>=1;--i)
{
if(s[i]==s[i+1]) rig[i]=i+1;
else rig[i]=rig[i+2];
}
//for(int i=1;i<=n;++i) printf("%d ",rig[i]);
//puts("");
for(int k=1;k<=n;++k)
{
for(int l=1;l<=n;)
{
int L=l,R=n,res=n+1;
while(L<=R)
{
int mid=(L+R)>>1;
if(suml[mid]-suml[l-1]>=k || sumw[mid]-sumw[l-1]>=k) res=mid,R=mid-1;
else L=mid+1;
}
if(res>n) break;//[l,res],r\in [res,n]
if(abs(sum[res]-sum[l-1])>=2)
{
f[k]+=(sum[res]-sum[l-1]>=2?1:0);
l=res+1;
continue;
}
int t=sum[res]-sum[l-1];
if(!t)
{
if(res+1<=n && rig[res+1]<=n)
{
f[k]+=(sum[rig[res+1]]-sum[l-1]>=0?1:0);
l=rig[res+1]+1;
}
else break;
}
else
{
if(res+1>n) break;
if(t==1 && s[res+1]=='W') f[k]++,l=res+2;
else if(t==-1 && s[res+1]=='L') l=res+2;
else if(rig[res]<=n)
{
f[k]+=(sum[rig[res]]-sum[l-1]>=2?1:0);
l=rig[res]+1;
}
else break;
}
}
}
int ans=0;
for(int i=1,pw=1;i<=n;++i)
{
up(ans,mul(f[i],pw));
pw=mul(pw,n+1);
}
printf("%d\n",(ans%mod+mod)%mod);
return 0;
}
随便找的 1 log 1\log 1log
#include<iostream>
#include<algorithm>
#include<functional>
#include<vector>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
const int inf = 1e9;
const int max_n = 1e6 + 1000;
int prew[max_n], prel[max_n];
int id1[max_n],id2[max_n];
int tiebreak[2][max_n];
char s[max_n];
int n;
inline ll solve(int k)
{
int ans = 0;
int L = 1;
while (L <= n)
{
int R1 = inf, R2 = inf;
int r1;
if (prew[L - 1] + k > n)r1=inf;
else r1 = id1[prew[L - 1] + k];
if (r1==0)r1=inf;
if (r1 <= n && r1-L+1-k<=k+2)
{
if (r1-L+1-k<=k-2)R1=r1;
else if (r1-L+1-k==k-1)R1 = tiebreak[0][r1+1];
else if (r1-L+1-k==k)
{
R1 = tiebreak[0][r1+1];
if (R1!=inf)R1 = tiebreak[0][R1+1];
}
else if (r1-L+1-k==k+1)
{
R1 = tiebreak[0][r1+1];
if (R1!=inf)
{
R1 = tiebreak[0][R1+1];
if (R1!=inf)R1=tiebreak[0][R1+1];
}
}
}
if (prel[L - 1] + k > n)r1=inf;
else r1 = id2[prel[L - 1]+k];
if (r1==0)r1=inf;
if (r1 <= n && r1-L+1-k<=k+2)
{
if (r1-L+1-k<=k-2)R2=r1;
else if (r1-L+1-k==k-1)R2 = tiebreak[1][r1+1];
else if (r1-L+1-k==k)
{
R2 = tiebreak[1][r1+1];
if (R2!=inf)R2 = tiebreak[1][R2+1];
}
else if (r1-L+1-k==k+1)
{
R2 = tiebreak[1][r1+1];
if (R2!=inf)
{
R2 = tiebreak[1][R2+1];
if (R2!=inf)R2=tiebreak[1][R2+1];
}
}
}
L = min(R1, R2) + 1;
if (R1 < R2 && R1 <= n)++ans;
}return ans;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n;
for (int i = 1;i <= n;++i)cin >> s[i];
int tot1=0,tot2=0;
for (int i = 1;i <= n;++i)
{
prew[i] = prew[i - 1] + (s[i] == 'W');
prel[i] = prel[i - 1] + (s[i] == 'L');
if (s[i]=='W')id1[++tot1]=i;
else id2[++tot2]=i;
}
tiebreak[0][n+1]=tiebreak[1][n+1]=inf;
if (s[n]=='W')tiebreak[0][n]=n,tiebreak[1][n]=inf;
else tiebreak[0][n]=inf,tiebreak[1][n]=n;
for (int i=n-1;i>=1;--i)
{
if (s[i]=='W')
{
tiebreak[0][i]=i;
if (tiebreak[1][i+1]==inf)tiebreak[1][i]=inf;
else tiebreak[1][i]=tiebreak[1][tiebreak[1][i+1]+1];
}
else if (s[i]=='L')
{
tiebreak[1][i]=i;
if (tiebreak[0][i+1]==inf)tiebreak[0][i]=inf;
else tiebreak[0][i]=tiebreak[0][tiebreak[0][i+1]+1];
}
}
ll ans = 0, PW = 1;
for (int i = 1;i <= n;++i)
{
int Fi;
Fi = solve(i);
ans = (ans + Fi * PW % mod) % mod;
PW = PW * (n + 1) % mod;
}
cout << ans << endl;
}
D. Double Strings
【题目】
给定两个字符串 A , B A,B A,B,问有多少中方式,在 A A A和 B B B中各选一个长度相同的子序列,满足在 A A A中选的字典序小于在 B B B中选的。(字符位置不同即为不同)
∣ A ∣ , ∣ B ∣ ≤ 5000 |A|,|B|\leq 5000 ∣A∣,∣B∣≤5000
【思路】
考虑我们怎么构造这个字典序小于的情况,必然是一段连续一样的+一个小于的字符+一段任意的
我们考虑枚举这个小于的字符,然后求出前后的情况总数。
对于前面连续一段一样的,我们可以设 f [ i ] [ j ] f[i][j] f[i][j]表示 A A A到第 i i i位, B B B到第 j j j位的相同子序列个数,转移有点类似二维前缀和。
对于后面一段任意的,设 A A A中剩下 n n n个字符可选, B B B中剩下 m m m个字符可选,那答案就是 ∑ i = 0 min ( n , m ) ( n i ) ( m i ) \sum_{i=0}^{\min(n,m)}\binom{n}{i}\binom{m}{i} ∑i=0min(n,m)(in)(im),事实上经过推导可以发现这个东西是 ( n + m n ) \binom{n+m}{n} (nn+m)
那么总的复杂度 O ( l e n 2 ) O(len^2) O(len2)
【参考代码】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=5005,mod=1e9+7;
int n,m;
int f[N][N],fac[N<<1],ifac[N<<1];
char s[N],t[N];
int read()
{
int ret=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return f?ret:-ret;
}
int upm(int x){return x>=mod?x-mod:(x<0?x+mod:x);}
void up(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
void up(ll &x,ll y){x=(x+y>=mod?x+y-mod:x+y);}
int mul(int x,int y){return 1ll*x*y%mod;}
int qpow(int x,int y)
{
int ret=1;
for(;y;y>>=1,x=mul(x,x))
if(y&1) ret=mul(ret,x);
return ret;
}
int C(int n,int m)
{
if(n<m) return 0;
if(n==m || !m) return 1;
return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main()
{
scanf("%s%s",s+1,t+1);n=strlen(s+1);m=strlen(t+1);
fac[0]=ifac[0]=1;
for(int i=1;i<N*2;++i) fac[i]=mul(fac[i-1],i),ifac[i]=qpow(fac[i],mod-2);
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(s[i]==t[j]) f[i][j]=upm(f[i-1][j]+f[i][j-1]+1);
else f[i][j]=upm(f[i-1][j]+f[i][j-1]-f[i-1][j-1]);
//printf("%d %d %d\n",i,j,f[i][j]);
}
}
ll ans=0;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)
{
if(s[i]<t[j])
{
up(ans,1ll*upm(f[i-1][j-1]+1)*C(n-i+m-j,m-j)%mod);
//printf("%d %d %d %d\n",i,j,f[i-1][j-1]+1,C(n-i+m-j,m-j));
}
}
printf("%lld\n",(ans%mod+mod)%mod);
return 0;
}
E. Eert Esiwtib
【题目】
给定一颗 n n n个点的带权有根树,每条边有一个位运算符号。 q q q次询问给定 d d d和 x x x,表示将编号 i i i的节点权值变为 i ⋅ d + a i i\cdot d+a_i i⋅d+ai后,子树 x x x中,所有节点到 x x x的路径权值经过边上位运算操作后的值的某种位运算和(或和,与和,异或和)。
n , q ≤ 1 0 5 , a i ≤ 1 0 18 , d ≤ 100 n,q\leq 10^5,a_i\leq 10^{18},d\leq 100 n,q≤105,ai≤1018,d≤100
【思路】
首先这个 d d d只有100,暗示我们实际上可以对每个 d d d做树DP
然后设 f [ u ] [ 0 / 1 / 2 ] f[u][0/1/2] f[u][0/1/2]表示 u u u 到以 u u u为根的子树中所有点的路径的权值的 或和/与和/异或和。转移的话也是分情况讨论,考虑 u − > v u->v u−>v 这条边,以边权是“|”符号的情况为例。
-
( w u ∣ v 1 ) ∣ ( w u ∣ v 2 ) ∣ . . . = w u ∣ ( v 1 ∣ v 2 ∣ . . . ) (w_u|v_1) | (w_u|v_2) | ... = w_u | (v_1 | v_2 | ...) (wu∣v1)∣(wu∣v2)∣...=wu∣(v1∣v2∣...),故 f [ u ] [ 0 ] ∣ = w u ∣ f [ v ] [ 0 ] f[u][0] |= w_u | f[v][0] f[u][0]∣=wu∣f[v][0],同理有 f [ u ] [ 1 ] & = w u ∣ f [ v ] [ 1 ] f[u][1] \&= w_u | f[v][1] f[u][1]&=wu∣f[v][1]
-
KaTeX parse error: Expected '}', got '^' at position 15: (w_u|v1)\text{^̲} (wu|v2) \text…,这里得分情况讨论。首先 w u w_u wu 或进来不会影响 ∼ w u \sim w_u ∼wu 的那些位,所以首先有 KaTeX parse error: Expected '}', got '^' at position 15: f[u][2] \text{^̲}= \sim w_u \& …,然后考虑 w u w_u wu 的贡献,如果 u u u 为根的子树大小是奇数,那么 w u w_u wu 相当于被异或了奇数次,贡献则就是 w u w_u wu,否则如果子树大小是偶数,那么 w u w_u wu 就被异或了偶数次,贡献是 0。综上,有$ f[u][2] \text{^}= (Size[u] & 1 ? w_u : 0) | (\sim w_u & f[v][2])$。
边权是剩下两种符号的转移也是用类似的思路去推导,总之就是考虑奇偶性之类的,这里就不过多赘述了。
复杂度 O ( n d ) O(nd) O(nd)
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define pb push_back
#define hvie '\n'
using namespace std;
typedef long long ll;
ll yh(){
ll ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
const int maxn=1e5+5;
int n,q;
vector<pair<int,int>>adj[maxn];
ll a[maxn];
ll And[105][maxn],Or[105][maxn],Xor[105][maxn];
int sz[maxn];
void dfs(int x,ll *And,ll *Or,ll *Xor){
sz[x]=1;
And[x]=(1ll<<62)-1;
for(auto &[y,op]:adj[x]){
dfs(y,And,Or,Xor);
sz[x]+=sz[y];
ll o=Or[y]|a[y];
ll d=And[y]&a[y];
ll e=Xor[y]^a[y];
switch(op){
case 0:{
Or[x]|=(o|a[x]);
And[x]&=(d|a[x]);
if(sz[y]&1) Xor[x]^=a[x];
Xor[x]^=(~a[x])&e;
break;
}
case 1:{
Or[x]|=o&a[x];
And[x]&=d&a[x];
Xor[x]^=e&a[x];
break;
}
case 2:{
Or[x]|=(o&(~a[x]))|((~d)&a[x]);
And[x]&=((~o)&a[x])|(d&(~a[x]));
Xor[x]^=(sz[y]&1)?e^a[x]:e;
break;
}
}
}
}
int main(){
n=yh(),q=yh();
rep(i,1,n) a[i]=yh();
rep(i,2,n){
int f=yh(),s=yh();adj[f].pb({i,s});
}
rep(d,0,100){
dfs(1,And[d],Or[d],Xor[d]);
rep(i,1,n) a[i]+=i;
}
rep(i,1,q){
int d=yh(),u=yh();
cout<<Or[d][u]<<" "<<And[d][u]<<" "<<Xor[d][u]<<hvie;
}
return 0;
}
F. Finding Points
【题目】
有二维平面上有 n n n个点 A i A_i Ai逆十字排布,形成一个凸包,现在要在凸包内求一个点 P P P,使得 ∠ A i P A i + 1 \angle A_iPA_{i+1} ∠AiPAi+1的最小值最大。
4 ≤ n ≤ 100 , ∣ x i ∣ , ∣ y i ∣ ≤ 10000 4\leq n\leq 100,|x_i|,|y_i|\leq 10000 4≤n≤100,∣xi∣,∣yi∣≤10000
【思路】
考虑固定 x p x_p xp,其实我们可以发现这个角的函数关于 y y y是单峰的,那么固定 y p y_p yp也是一样。
于是我们三分套三分即可。
复杂度 O ( n log 2 W ) O(n\log ^2W) O(nlog2W)
出题人给了一个更复杂的做法:
首先二分答案 x x x,然后对于凸包上的每条边 ( A i , A j ) (A_i, A_j) (Ai,Aj),可以知道 P P P 的合法范围一定在经过这两个点的某个圆的一个弓形上,具体可以用圆周角的那一套理论去推导,然后问题就变成求 n n n 个弓形是否有公共点了,这题的话因为 n ≥ 4 n\geq 4 n≥4,不会出现 n n n 个圆只在凸包外有交点的情况,所以就看 n n n 个圆是否有交。但是,除了考虑圆的交,还得考虑和凸包本身是否相交,所以还得写一个圆的交然后再跟凸包求交。
(出题人说这个三分套三分不靠谱,实际上靠谱的很,比赛的时候数据锅了, P P P在凸包外,赛后重测我们居然是一血233)
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
const int maxn=3e5+5;
const db eps=1e-12;
int n;
struct poi{
double x,y;
poi(){}
poi(double x,double y):x(x),y(y){}
}p[maxn];
poi operator-(const poi&A,const poi&B){
return poi(A.x-B.x,A.y-B.y);
}
db dot(poi a,poi b){
return a.x*b.x+a.y*b.y;
}
db mo(poi a){return sqrt(dot(a,a));}
double ang(poi a,poi b){
return acos(dot(a,b)/mo(a)/mo(b));
}
db crs(poi a,poi b){
return a.x*b.y-a.y*b.x;
}
bool check(poi G){
rep(i,1,n){
if(crs(p[i]-G,p[i%n+1]-G)<0) return 0;
}
return 1;
}
db calc(poi G){
db ag=1e10;
if(!check(G)) return -1e10;
rep(i,1,n){
ag=min(ag,ang(p[i]-G,p[i%n+1]-G));
}
return ag;
}
db xmax,ymax;
db xmin,ymin;
db calc1(db x){
db ly=ymin,ry=ymax;
// cout<<x<<" : ";
while(ly+eps<ry){
// cout<<ly<<" "<<ry<<hvie;
db lmid=ly*2.0/3+ry/3.0;
db rmid=ly/3.0+ry*2.0/3;
if(calc(poi(x,lmid))>calc(poi(x,rmid))) ry=rmid;
else ly=lmid;
}
return calc(poi(x,ly));
}
int main(){
n=yh();
poi G;
xmax=-1e10,ymax=-1e10;
xmin=1e10,ymin=1e10;
rep(i,1,n){
p[i].x=yh();p[i].y=yh();
G.x+=p[i].x;
G.y+=p[i].y;
xmin=min(xmin,p[i].x);
ymin=min(ymin,p[i].y);
xmax=max(xmax,p[i].x);
ymax=max(ymax,p[i].y);
}
G.x/=n;G.y/=n;
double pi=acos(-1);
db lx=xmin,rx=xmax;
while(lx+eps<rx){
db lmid=lx*2.0/3+rx/3.0;
db rmid=lx/3.0+rx*2.0/3;
if(calc1(lmid)>calc1(rmid)) rx=rmid;
else lx=lmid;
}
cout<<fixed<<setprecision(12)<<calc1(lx)*180./pi<<hvie;
return 0;
}
G. Greater Interger, Better LCM
【题目】
给定三个数 a , b , c a,b,c a,b,c,求 x , y x,y x,y满足 lcm ( a + x , b + y ) = c \text{lcm} (a+x,b+y)=c lcm(a+x,b+y)=c,且 x + y x+y x+y最小。 c c c以 ∏ i = 1 n p i q i \prod_{i=1}^np_i^{q_i} ∏i=1npiqi形式给出,其中 p i p_i pi是素数。
n ≤ 18 , p i ≤ 100 , ∑ q i ≤ 18 , a , b , c ≤ 1 0 32 n\leq 18,p_i\leq 100,\sum q_i\leq 18,a,b,c\leq 10^{32} n≤18,pi≤100,∑qi≤18,a,b,c≤1032
【思路】
这个数据范围小的一,我们考虑乱搞。
首先它给的这个 c c c形式就很有意思,摆明了让我们枚举这个最大的 q i q_i qi分配到哪个数上,于是问题就变成了先分配这个 q i q_i qi。我们设 f [ s ] f[s] f[s]表示分配给 a + x a+x a+x的最大 q i q_i qi状态为 s s s,那么接下来就要求出这个状态下加入其他一些质因子的满足乘积 ≥ a \geq a ≥a的最小乘积(有点绕),这个状态不是很多,因为质因子不多,而且一个质因子用的次数也不多。然后再做一个传递闭包就行。另一边同理。
最后合并一下就行了。
复杂度 O ( n ⋅ 2 n + 搜 索 ) O(n\cdot 2^n+搜索) O(n⋅2n+搜索)
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef unsigned long long ull;
typedef double db;
typedef __int128_t Int;
Int yh(){
Int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
int n;
Int inf=1;
vector<pii> c;
char stk[1000];int top_;
void print(Int x){
if(x==0) {puts("0");return;}
top_=0;
while(x) stk[++top_]=x%10+'0',x/=10;
dwn(i,top_,1) cout<<stk[i];
cout<<hvie;
}
Int a,b;
Int f[1<<20],g[1<<20];
void dfs(Int *f,Int tar,Int x,int id,int S){
if(x>f[S]) return;
if(id==n){
if(x>=tar)f[S]=min(f[S],x);
return;
}
for(int cnt=0,D=1;cnt<=c[id].se;cnt++,D*=c[id].fi){
dfs(f,tar,x*D,id+1,S|((cnt==c[id].se)*(1<<id)));
}
}
int main(){
n=yh();
rep(i,1,31) inf*=10;
rep(i,1,n){
int x=yh(),y=yh();
c.pb({x,y});
}
a=yh();b=yh();
int mask=(1<<n)-1;
rep(i,0,mask) f[i]=g[i]=inf;
dfs(f,a,1,0,0);
dfs(g,b,1,0,0);
dwn(s,mask,0)rep(i,0,n-1){
if(s>>i&1)g[s^(1<<i)]=min(g[s^(1<<i)],g[s]);
}
Int ans=inf;
rep(i,0,mask){
ans=min(ans,f[i]+g[mask^i]);
}
ans-=a+b;
print(ans);
return 0;
}
H. Holding Two
【题意】
给定 n , m n,m n,m,求一个01矩阵 A A A满足没有任意三个位置 a , b , c a,b,c a,b,c,满足 A a = A b = A c A_a=A_b=A_c Aa=Ab=Ac且 a x − b x = b x − c x , a y − b y = b y − c y a_x-b_x=b_x-c_x,a_y-b_y=b_y-c_y ax−bx=bx−cx,ay−by=by−cy
n , m ≤ 1000 n,m\leq 1000 n,m≤1000
【思路】
令 A [ i ] [ j ] = ( i + ( j / 2 ) ) % 2 A[i][j]=(i+(j/2))\%2 A[i][j]=(i+(j/2))%2即可(从0开始)。
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
const int maxn=3e5+5;
int n,m;
int main(){
n=yh(),m=yh();
rep(i,1,n){
if(i&1){
rep(j,1,m){
cout<<((j-1)/2&1);
}
}
else{
rep(j,1,m){
cout<<((((j-1)/2)&1)^1);
}
}
puts("");
}
return 0;
}
I. Interval Queries
【题目】
给定一个长度为 n n n序列 A i A_i Ai,有 q q q个询问给出 ( l , r , k ) (l,r,k) (l,r,k),计算 ∑ i = 0 k − 1 f ( { A l − i , . . . , A l , A l + 1 , . . . , A r , . . . , A r i } ) \sum_{i=0}^{k-1}f(\{A_{l-i},...,A_l,A_l+1,...,A_r,...,A_{r_i}\}) ∑i=0k−1f({Al−i,...,Al,Al+1,...,Ar,...,Ari}),其中 f f f表示最长连续值域段的长度。
n , q ≤ 1 0 5 , A i ≤ n , ∑ k ≤ 1 0 7 n,q\leq 10^5,A_i\leq n,\sum k\leq 10^7 n,q≤105,Ai≤n,∑k≤107
【思路】
回滚莫队+链表即可。
回滚的一个实现方式:先把所有询问按照 l K \frac l K Kl 分组,然后分配每组询问,即按 r r r从小到大排序,并找到最大的 l l l(记为 m x l mxl mxl),如果有某个询问的 r < m x l r < mxl r<mxl,直接暴力处理这个询问,否则就可以维护一个 [ m x l , R ] [mxl, R] [mxl,R] 的区间,每次向右拓展 R R R,处理某个询问的时候就把左端点从 m x l mxl mxl 拓展到询问的 l l l,算下当前答案,再把左端点退回 m x l mxl mxl,这期间只有加入和撤销操作。
这个题莫队套上的操作就是维护双向链表以及最长的链表的长度,所以加点的时候可以先把当前答案和所加的点存下来再维护答案,这样就可以很方便地进行撤销了。
复杂度 O ( n n + k ) O(n\sqrt n +k) O(nn+k)
【参考代码】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=1e5+10,mod=998244353;
int n,m,B,lim;
int a[N],pw[N],ans[N];
int read()
{
int ret=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return f?ret:-ret;
}
int upm(int x){return x>=mod?x-mod:x;}
void up(int &x,int y){x=upm(x+y);}
int mul(int x,int y){return 1ll*x*y%mod;}
struct Tquery
{
int l,r,k,id;
Tquery(int l=0,int r=0,int k=0,int id=0):l(l),r(r),k(k),id(id){}
bool operator < (const Tquery&rhs)const{
return r<rhs.r;
}
};
vector<Tquery>vec[330];
struct node
{
int tans,x,l,r;
node(int tans,int x,int l,int r):tans(tans),x(x),l(l),r(r){}
};
stack<node>st;
int maxans;
int L[N],R[N],cnt[N];
void add(int x)
{
if(cnt[x]++) return;
st.push(node(maxans,x,L[x],R[x]));
int l=L[x],r=R[x];
if(l>=1) R[l]=r;
if(r<=n) L[r]=l;
//printf("%d %d %d %d\n",x,l,r,maxans);
maxans=max(maxans,r-l-1);
}
void del(int x)
{
if(--cnt[x]) return;
node t=st.top();st.pop();
L[x]=t.l;R[x]=t.r;
if(t.l>=1) R[t.l]=x;
if(t.r<=n) L[t.r]=x;
maxans=t.tans;
}
void solve(Tquery x)
{
//printf("!!!%d\n",maxans);
ans[x.id]=maxans;
for(int i=1;i<x.k;++i)
{
add(a[x.r+i]);
add(a[x.l-i]);
up(ans[x.id],mul(maxans,pw[i]));
}
for(int i=x.k-1;i>=1;--i)
{
del(a[x.l-i]);
del(a[x.r+i]);
}
}
void init()
{
for(int i=1;i<=n;++i) L[i]=i-1,R[i]=i+1,cnt[i]=0;
maxans=0;
}
int main()
{
n=read();m=read();lim=330;
pw[0]=1;
for(int i=1;i<=n;++i) a[i]=read(),pw[i]=mul(pw[i-1],n+1);
for(int i=1;i<=m;++i)
{
int l=read(),r=read(),k=read();
if(r-l+1<=lim) vec[0].pb(Tquery(l,r,k,i));
else vec[l/lim+1].pb(Tquery(l,r,k,i));
}
for(auto v:vec[0])
{
init();
for(int i=v.l;i<=v.r;++i) add(a[i]);
solve(v);
for(int i=v.r;i>=v.l;--i) del(a[i]);
}
for(int bl=1,S=n/lim+1;bl<=S;++bl)
{
init();
int tmp=bl*lim,nowl=tmp,nowr=nowl-1;
sort(vec[bl].begin(),vec[bl].end());
for(auto i:vec[bl])
{
for(;nowr<i.r;) add(a[++nowr]);
for(;nowl>i.l;) add(a[--nowl]);
solve(i);
for(;nowl<tmp;) del(a[nowl++]);
}
}
for(int i=1;i<=m;++i) printf("%d\n",(ans[i]%mod+mod)%mod);
return 0;
}
J. Jewels
【题目】
n n n个点,第 i i i个点用 ( x i , y i , z i , v i ) (x_i,y_i,z_i,v_i) (xi,yi,zi,vi)表示,现在要以某个顺序标记所有点,第 t t t个标记的点花费 x i 2 + y i 2 + ( z i + t × v i ) 2 x_i^2+y_i^2+(z_i+t\times v_i)^2 xi2+yi2+(zi+t×vi)2,求最小花费。
n ≤ 300 , 0 ≤ x i , y i , z i , v i ≤ 1000 n\leq 300,0\leq x_i,y_i,z_i,v_i\leq 1000 n≤300,0≤xi,yi,zi,vi≤1000
【思路】
一个点可以拆成 n n n个时间,然后每个时间只能对应一个点,就是最小权匹配了。
【参考代码】
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 505;
int n;
namespace KM {
const int MAXN = 505;
const long long INF = 1e18;
int link[MAXN], pre[MAXN], mat[MAXN];
long long mp[MAXN][MAXN], lx[MAXN], ly[MAXN], slack[MAXN];
bool visx[MAXN], visy[MAXN];
inline void aug(int s) {
while (s) {
int t = mat[pre[s]];
link[s] = pre[s];
mat[link[s]] = s;
s = t;
}
return;
}
void bfs(int s) {
long long delta;
fill(slack + 1, slack + 1 + n, INF);
fill(visx + 1, visx + 1 + n, 0);
fill(visy + 1, visy + 1 + n, 0);
queue<int> q;
while (!q.empty()) q.pop();
q.push(s);
while (true) {
while (!q.empty()) {
int fr = q.front();
q.pop();
visx[fr] = 1;
for (int i = 1; i <= n; ++i) {
if (visy[i]) continue;
delta = lx[fr] + ly[i] - mp[fr][i];
if (!delta) {
visy[i] = 1;
pre[i] = fr;
if (!link[i]) return aug(i);
q.push(link[i]);
} else if (slack[i] > delta) slack[i] = delta, pre[i] = fr;
}
}
delta = INF;
for (int i = 1; i <= n; ++i) if (!visy[i]) delta = min(delta, slack[i]);
for (int i = 1; i <= n; ++i) {
if (visx[i]) lx[i] -= delta;
if (visy[i]) ly[i] += delta;
else slack[i] -= delta;
}
for (int i = 1; i <= n; ++i) {
if (!visy[i] && !slack[i]) {
visy[i] = 1;
if (!link[i]) return aug(i);
q.push(link[i]);
}
}
}
return;
}
long long KM() {
long long ans = 0;
fill(lx + 1, lx + 1 + n, -INF);
for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) lx[i] = max(lx[i], mp[i][j]);
for (int i = 1; i <= n; ++i) bfs(i);
for (int i = 1; i <= n; ++i) ans += lx[i] + ly[i];
return ans;
}
}
struct Point {
int x, y, z, v;
} a[MAXN];
long long sqr(long long x) {
return x * x;
}
long long cal(int x, int t) {
return sqr(a[x].x) + sqr(a[x].y) + sqr(a[x].z + a[x].v * t);
}
signed main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d%d%d%d", &a[i].x, &a[i].y, &a[i].z, &a[i].v);
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
KM::mp[i][j] = -cal(j, i - 1);
}
}
printf("%lld\n", -KM::KM());
return 0;
}
K. King of Range
【题目】
给定一个长度为 n n n的序列 a i a_i ai,有 m m m个询问,给出一个常数 k k k,问有多少个区间 [ l , r ] [l,r] [l,r]满足区间所有数字极差大于 k k k。
n ≤ 1 0 5 , m ≤ 200 , a i , k ≤ 1 0 9 n\leq 10^5,m\leq 200,a_i,k\leq 10^9 n≤105,m≤200,ai,k≤109
【思路】
一个无脑的做法是,首先RMQ预处理出区间最小值最大值。对于每个询问,双指针扫一下就行了。
复杂度 O ( n log n + n m ) O(n\log n+nm) O(nlogn+nm)
【参考代码】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=1e5+10,mod1=1e9+7,mod2=1e9+9;
int n,m;
int a[N],mi[19][N],mx[19][N];
int Log[N];
int read()
{
int ret=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return f?ret:-ret;
}
int getmin(int l,int r)
{
int k=Log[r-l+1];
return min(mi[k][l],mi[k][r-(1<<k)+1]);
}
int getmax(int l,int r)
{
int k=Log[r-l+1];
return max(mx[k][l],mx[k][r-(1<<k)+1]);
}
int main()
{
n=read();m=read();
for(int i=2;i<N;++i) Log[i]=Log[i>>1]+1;
for(int i=1;i<=n;++i) a[i]=read(),mi[0][i]=mx[0][i]=a[i];
for(int j=1;j<19;++j)
{
for(int i=1;i<=n;++i)
if(i+(1<<j)-1<=n)
{
mi[j][i]=min(mi[j-1][i],mi[j-1][i+(1<<(j-1))]);
mx[j][i]=max(mx[j-1][i],mx[j-1][i+(1<<(j-1))]);
}
}
while(m--)
{
int k=read();
int r=1;ll ans=0;
for(int i=1;i<=n;++i)
{
int tmin=getmin(i,r),tmax=getmax(i,r);
while(tmax-tmin<=k && r<=n)
{
++r;
if(r>n) break;
tmin=getmin(i,r),tmax=getmax(i,r);
}
if(r>n) break;
ans+=(n-r+1);
}
printf("%lld\n",ans);
}
return 0;
}