2023.11.8
A
\(n\times m\) 的网格图,初始一个数 \(o=0\)。
-
\(o=0\) 时只能上下走,\(o=1\) 时只能左右走,走一步代价为 \(1\)。
-
给出 \(k\) 个点,在这些点可以令 \(o\leftarrow o\oplus 1\),代价为 \(1\)。
问 \((1,1)\) 到 \((n,m)\) 的最小代价。
\(1\le n,m\le 10^5\),\(1\le k\le 2\times 10^5\)。
拆点建图跑最短路即可。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define N 200010
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,m,x[N],y[N],_k,k;
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
vector<pii>gx[N],gy[N];
vector<pii>e[N<<1];//goud -> 0 golr -> 1
void add(int u,int v,int w){
e[u].push_back(mp(v,w));
}
ll dist[N<<1];bool vis[N<<1];
#define pli pair<ll,int>
priority_queue<pii>q;
void dijkstra(int S){
memset(dist,0x3f,sizeof(dist));
dist[S]=0,q.push(mp(0,S));
while(!q.empty()){
int u=q.top().se;q.pop();
if(vis[u])continue;
vis[u]=true;
for(pii E:e[u]){
if(dist[E.fi]>dist[u]+E.se){
dist[E.fi]=dist[u]+E.se;
q.push(mp(-dist[E.fi],E.fi));
}
}
}
}
int main(){
n=read(),m=read(),_k=read();
x[1]=1,y[1]=1,k=1;
for(int i=1,_x,_y;i<=_k;i++){
_x=read(),_y=read();
if(_x==1&&_y==1)add(1,2,1);
else if(_x!=n||_y!=m)x[++k]=_x,y[k]=_y;
}
x[++k]=n,y[k]=m;
for(int i=1;i<=k;i++){
gx[x[i]].push_back(mp(y[i],i));
gy[y[i]].push_back(mp(x[i],i));
}
for(int i=1,len;i<=n;i++){
sort(gx[i].begin(),gx[i].end());
len=gx[i].size();
for(int j=0,u,v,up,vp;j<len-1;j++){
u=gx[i][j].se,v=gx[i][j+1].se;
up=gx[i][j].fi,vp=gx[i][j+1].fi;
add(u*2-1,v*2-1,vp-up),add(v*2-1,u*2-1,vp-up);
add(u*2-1,v*2,vp-up+1),add(v*2-1,u*2,vp-up+1);
}
}
for(int i=1,len;i<=m;i++){
sort(gy[i].begin(),gy[i].end());
len=gy[i].size();
for(int j=0,u,v,up,vp;j<len-1;j++){
u=gy[i][j].se,v=gy[i][j+1].se;
up=gy[i][j].fi,vp=gy[i][j+1].fi;
add(u*2,v*2,vp-up),add(v*2,u*2,vp-up);
add(u*2,v*2-1,vp-up+1),add(v*2,u*2-1,vp-up+1);
}
}
dijkstra(1);
ll ans=min(dist[k*2-1],dist[k*2]);
if(ans==dist[0])puts("-1");
else printf("%lld\n",ans);
return 0;
}
B
Korney Korneevich and XOR (hard version)
*2400。
问 \(\{a_n\}\) 的所有递增子序列的异或和,输出这些值。
\(1\le n\le 10^6\),\(0\le a_i\le 5000\)。
注意到异或和在 \([0,2^{13})\) 之间,记 \(V=2^{13}-1\)。
记 \(f_i\) 为当前以 \(a\) 结尾的子序列的异或值集合,其中 \(a<i\)。
对于每个 \(a\),对 \(b\in f_a,j\in[a+1,V]\),将 \(a\oplus b\) 加入 \(f_j\)。
每次即值域后缀集合加点,对于异或值 \(p\) 记录 \(mx_p\) 表示未添加 \(p\) 的最大集合编号。
时间复杂度 \(O(n+V^2)\)。
点击查看代码
#include<bits/stdc++.h>
#define V 8192
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,mx[V],ans=1;
vector<int>d[V];bool vis[V];
int main(){
n=read(),vis[0]=true;
for(int i=1;i<V;i++)d[i].push_back(0),mx[i]=V-1;
for(int i=1,x;i<=n;i++){
x=read();
for(int v:d[x]){
int p=v^x;ans+=!vis[p],vis[p]=true;
while(mx[p]>x)d[mx[p]--].push_back(p);
}
d[x].clear();
}
printf("%d\n",ans);
for(int i=0;i<V;i++)
if(vis[i])printf("%d ",i);
printf("\n");
return 0;
}
C
P8313 [COCI2021-2022#4] Izbori
问 \(\{a_n\}\) 有多少区间存在绝对众数。
\(1\le n\le 2\times 10^5\),\(1\le a_i\le 10^9\)。
典,但是没放根号做法。
-
\(O(n\sqrt{n})\) Sol
根号分治。
先离散化。对于出现次数 \(\ge\sqrt{n}\) 的数 \(x\),将 \(x\) 赋权 \(1\),否则为 \(-1\),作前缀和后数对 \([l,r]\) 合法即 \(S_r>S_{l-1}\),\(O(n\sqrt{n}+n\log n)\) 数点即可。
对于出现次数 \(<\sqrt{n}\) 的,注意到 \(\sum cnt(x)^2\sim O(n\sqrt{n})\),考虑 \(O(1)\) 计算一段整块和两边散块的答案,维护直线交点即可。
总时间复杂度 \(O(n\sqrt{n})\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define N 200010
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,dlt,maxn,a[N],b[N],len;
int cnt[N];
vector<int>s[N];
#define lb(x) x&-x
struct Fenwick{
int c[N<<1];
void add(int x,int k){
for(;x<=n+dlt;x+=lb(x))c[x]+=k;
}
int qry(int x){
int ret=0;
for(;x;x-=lb(x))ret+=c[x];
return ret;
}
}B;
ll ans;
ll calc(int p,int q,int t){
if(t<0)return 0;
p=min(p,t),q=min(q,t);
int pos=min(t-q,p);
ll ret=1ll*q*(pos+1);
if(p>pos)ret+=1ll*t*(p-pos)-1ll*p*(p+1)/2+1ll*pos*(pos+1)/2;
return ret+p+1;
}
int main(){
n=read(),maxn=sqrt(n),dlt=n+1;
for(int i=1;i<=n;i++)
a[i]=b[i]=read();
sort(b+1,b+1+n),len=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+1+len,a[i])-b;
cnt[a[i]]++,s[a[i]].push_back(i);
}
for(int i=1;i<=len;i++){
if(cnt[i]<=maxn){
int ln=s[i].size(),l,r,L,R;
for(int j=0;j<ln;j++)
for(int k=j;k<ln;k++){
l=s[i][j],r=s[i][k];
L=j?s[i][j-1]+1:1,R=(k<ln-1)?s[i][k+1]-1:n;
ans+=calc(l-L,R-r,2*(k-j+1)-(r-l+1)-1);
}
}
else{
int sum=0;
B.add(dlt,1);
for(int j=1;j<=n;j++){
sum+=(a[j]==i)?1:-1;
ans+=B.qry(sum+dlt-1),B.add(sum+dlt,1);
}
B.add(dlt,-1),sum=0;
for(int j=1;j<=n;j++)
sum+=(a[j]==i)?1:-1,B.add(sum+dlt,-1);
}
}
printf("%lld\n",ans);
return 0;
}
-
\(O(n\log n)\) Sol
考虑对每个 \(w\) 单独求解。对 \(w\) 赋权 \(1\),否则为 \(0\),那么 \([l+1,r]\) 合法即 \(2(S_r-S_l)>r-l\Rightarrow 2S_r-r>2S_l-l\)。
方便地,记 \(P_i=2S_i-i\)。
发现相邻的 \(P_i\) 一定相差 \(1\) 或 \(-1\)。维护这些 \(P_i\) 形成的若干递减的等差数列,能够发现所有 \(w\) 的总段数是 \(O(n)\) 的。
发现即支持区间加求二阶前缀和,就是单点加和三阶前缀和。
若 \(c\) 是 \(d\) 的三阶前缀和,则
使用三个树状数组维护。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define N 200010
#define lb(x) x&-x
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
const int delta=200003;
int n;
vector<ll>a[N];
ll c[5][N<<1],ans;
void add(int y,int x,ll val){
for(;x<=n+delta;x+=lb(x))c[y][x]+=val;
}
ll qry(int y,int x){
ll ret=0;
for(;x;x-=lb(x))ret+=c[y][x];
return ret;
}
void Add(ll l,ll r,ll val){
ll t0=l*l-3*l+2,t1=3-2*l,t2=1;
add(0,l,t0*val),add(1,l,t1*val),add(2,l,t2*val);
t0=-t0+(r-l+1)*(r-l+2)-2*r*(r-l+1),t1=-t1+2*(r-l+1),t2=-t2;
add(0,r+1,t0*val),add(1,r+1,t1*val),add(2,r+1,t2*val);
}
ll query(ll x){
ll ret=0;
ret=(qry(0,x)+qry(1,x)*x+qry(2,x)*x*x)/2;
return ret;
}
void solve(ll x){
ll cnt=0,_l=0,_r=0;
Add(delta,delta,1);
if(a[x][0]>1)
_l=-a[x][0]+1,_r=-1,Add(_l+delta,_r+delta,1);
for(int i=0;i<(int)a[x].size()-1;i++){
cnt++,_l=2*cnt-a[x][i+1]+1,_r=2*cnt-a[x][i];
ans+=query(_r-1+delta)-query(_l-2+delta);
Add(_l+delta,_r+delta,1);
}
cnt=0,Add(delta,delta,-1);
if(a[x][0]>1)
_l=-a[x][0]+1,_r=-1,Add(_l+delta,_r+delta,-1);
for(int i=0;i<(int)a[x].size()-1;i++){
cnt++,_l=2*cnt-a[x][i+1]+1,_r=2*cnt-a[x][i];
Add(_l+delta,_r+delta,-1);
}
}
map<int,int>mp;int sid;
int main(){
n=read();
for(int i=1,x;i<=n;i++){
x=read();
if(!mp[x])mp[x]=++sid;
a[mp[x]].push_back(i);
}
for(int i=1;i<=n;i++){
if(a[i].empty())continue;
a[i].push_back(n+1),solve(i);
}
printf("%lld\n",ans);
return 0;
}
D
Atcoder - Other Sponsored.
有意思的是 A 更逆天。
给出 \(\{a_n\}\),对于其 01 背包,每个数位上的权值是背包中所有元素在该数位上的最大值。
对每个数位的权值求和。
\(1\le n\le 20000\),\(1\le a_i\le 10^{12}\)。
对于 \(i\in[1,17]\),在模 \(10^i\) 意义下做背包,设 \(f_v\) 表示是否存在模 \(10^i\) 为 \(v\) 的方案。
我们想要的东西是 \(\max\limits_{f_v=1}v\) 的最高位。
考虑消除无用状态。
若三个合法状态 \(x,y,z\) 满足 \(x<y<z\) 且 \(z-x\le 10^{i-1}\),则 \(y\) 为无用状态。
考虑若之后选择了总和为 \(v\) 的数,\(z-x\le 10^{i-1}\Rightarrow (z+v)-(x+v)\le 10^{i-1}\)(模意义下),那么 \(z+v\) 和 \(x+v\) 的最高位的差的绝对值 \(\le 1\),\((y+v)\) 的最高位一定为其中之一,无需考虑。
故背包中合法状态个数 \(\le 10\).
可以做到 \(O(n\lg V)\).
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define N 20010
using namespace std;
ll read(){
ll x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n;ll a[N],x[N],m;
int ans;
int main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
for(ll i=1,pw=1;i<=17;i++){
ll P=pw*10;x[m=1]=0;
for(int j=1;j<=n;j++){
for(int k=1;k<=m;k++)
x[m+k]=(x[k]+a[j])%P;
sort(x+1,x+1+m*2);
int tot=2;
for(int k=3;k<=m*2;k++){
if(x[k]-x[tot-1]<pw)x[tot]=x[k];
else x[++tot]=x[k];
}
m=tot;
}
int mx=x[m]/pw;
ans+=mx,pw=P;
}
printf("%d\n",ans);
return 0;
}