联合省选2020 题解
Day1T1
- 记\(pre_{0/1},suf_{0/1}\)分别表示两种战士的能量前/后缀和
- 要求 \(\min\{pre_0[i],suf_1[i]\}\),而 \(suf_1[i]=sum_1-pre_1[i]+val_1[i]\)。
- 为了常数小我们考虑树状数组。
- 首先对温度离散化,因为答案一定是某个战士的温度(即在 \(x\) 中出现过的)。
- 然后,我们从前往后考虑每个询问:
- 首先,在树状数组上二分,求出满足 \(pre_0[i]\leqslant suf_1[i]\) 最大的 \(i\)。此时,设 \(pre_0[i]=v_0,pre_1[i]=v_1+val_1[i]\)。
- 若 \(v_0=v_1=0\),那么显然 \(\texttt{Peace}\)。
- 若 \(v_0>v_1\),那么 \(i\) 变大后答案最多为 \(v_1\),而此时答案为 \(v_0\),故最终位置即为 \(i\)。
- 否则,就把 \(i\) 再往后,使得答案为 \(v_1\),且 \(i\) 尽可能大。
#include<cstdio>
#include<algorithm>
using namespace std;
int Q,op[2100000],a[2100000],x[2100000],y[2100000];
int s0[2100000],s1[2100000];
int sum0,sum1,tree0[2100000],tree1[2100000];
int cnt,num[2100000];
char Getchar(){
static char now[1<<20],*S,*T;
if (T==S){
T=(S=now)+fread(now,1,1<<20,stdin);
if (T==S) return EOF;
}
return *S++;
}
int read(){
int x=0,f=1;
char ch=Getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') f=-1;
ch=Getchar();
}
while (ch<='9'&&ch>='0') x=x*10+ch-'0',ch=Getchar();
return x*f;
}
void add(int *tree,int x,int v){
for (;x<=cnt;x+=x&-x) tree[x]+=v;
}
void solve(){
int p=0,v,v0=0,v1=sum1,new_p;
for (int i=20;i>=0;i--){
new_p=p|(1<<i);
if (new_p<=cnt&&v0+tree0[new_p]<=v1-tree1[new_p]+s1[new_p]){
p=new_p;
v0+=tree0[p]; v1-=tree1[p];
}
}
if (!v0&&!v1) puts("Peace");
else
if (v0>v1) printf("%d %d\n",num[p],(v0<<1));
else{
p=0; v=sum1-v1;
for (int i=20;i>=0;i--){
new_p=p|(1<<i);
if (new_p<=cnt&&v>=tree1[new_p]){
p=new_p;
v-=tree1[p];
}
}
printf("%d %d\n",num[p+1],(v1<<1));
}
}
int main(){
Q=read(); int t;
for (int i=1;i<=Q;i++){
op[i]=read(); t=read();
if (op[i]==1){
a[i]=t;
x[i]=read(); y[i]=read();
num[++cnt]=x[i];
} else{
a[i]=a[t];
x[i]=x[t]; y[i]=-y[t];
}
}
sort(num+1,num+cnt+1);
cnt=unique(num+1,num+cnt+1)-num-1;
for (int i=1;i<=Q;i++) x[i]=lower_bound(num+1,num+cnt+1,x[i])-num;
for (int i=1;i<=Q;i++){
if (!a[i]) add(tree0,x[i],y[i]),s0[x[i]]+=y[i],sum0+=y[i];
else add(tree1,x[i],y[i]),s1[x[i]]+=y[i],sum1+=y[i];
solve();
}
return 0;
}
Day1T2
- 把普通多项式多项式转化成下降幂多项式的形式,\(f(k)=\sum_{i=0}^mb_ix^\underline i\)。
- 怎么转呢?利用 \(\displaystyle x^n=\sum_{k=1}^nS(n,k)x^\underline k\) 。其中第二类斯特林数 \(S(n,k)=S(n−1,k−1)+S(n−1,k)∗k\)。
\[\sum_{k=0}^nf(k)\times x^k \times \binom{n}{k}=\sum_{k=0}^n\sum_{i=0}^{\min(m,k)}b_ik^\underline ix^k\binom{n}{k}\\=\sum_{i=0}^mb_in^\underline i\sum_{k=i}^{n}x^k\binom{n-i}{k-i}\\=\sum_{i=0}^mb_in^\underline ix^{i}(x+1)^{n-i}
\]
#include<cstdio>
using namespace std;
typedef long long ll;
ll n,x,mod,m,ans;
ll a[1100],s[1100][1100];
ll qpow(ll x,ll a){
ll res=1;
while (a){
if (a&1) res=res*x%mod;
x=x*x%mod; a>>=1;
}
return res;
}
int main(){
scanf("%lld%lld%lld%lld",&n,&x,&mod,&m);
for (ll i=0;i<=m;i++) scanf("%lld",&a[i]);
s[0][0]=1;
for (ll i=1;i<=m;i++)
for (ll j=1;j<=i;j++)
s[i][j]=(s[i-1][j-1]+1ll*s[i-1][j]*j)%mod;
for (ll j=0;j<=m;j++){
ll tmp=1,xp=1;
for (ll i=0;i<=j;i++){
ans=(ans+1ll*a[j]*s[j][i]%mod*tmp%mod*xp%mod*qpow(x+1,n-i))%mod;
tmp=1ll*tmp*(n-i)%mod; xp=1ll*xp*x%mod;
}
}
printf("%lld\n",ans);
return 0;
}
Day1T3
30pts
- 强基交换定理:对于任意拟阵\(M\),若\(M\)有两个不相同的基\(A,B\),那么\(\forall x\in A\setminus B,\exists y\in B\setminus A,s.t.A-\{x\}+\{y\},B-\{y\}+\{x\}\)都是\(M\)的基。
- 我们只需考虑在 \(A/B\) 中删去一个元素,加入新的元素的基和它们的大小关系,只需枚举 \(A/B\) 中元素的价格,剩下的直接计算即可。
+10pts
- 原来的所有的 \(\leqslant,\geqslant\) 的关系都变成 \(=\) 的关系。
- 而点集 \(U\) 的 \(L_2\) 均值为其加权平均数 \(\displaystyle \frac{\sum_{v_i\in U}w_iy_i}{\sum_{v_i\in U}w_i}\)(导数易证,定义见高睿泉《浅谈保序回归问题》IOI2018国家集训队论文集)。并查集合并相等关系。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const ll INF=1ll<<60;
int n,m,a[1100],b[1100],fa[1100];
int v[1100],x[1100],minv,maxv;
bool trans[1100][1100],vis[1100];
ull c[1100],num[64]; ll ans;
int cnt; int pos[1100];
vector<int> vec[1100];
int findset(int x){
if (x!=fa[x]) fa[x]=findset(fa[x]);
return fa[x];
}
void Union(int x,int y){
x=findset(x); y=findset(y);
if (x!=y) fa[y]=x;
}
void ins(ull x){
for (int i=63;i>=0;i--)
if ((x>>i)&1){
if (!num[i]){ cnt++; num[i]=x; break;}
x^=num[i];
}
}
void work(){
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++){
cnt=0; memset(num,0,sizeof(num));
int tmp=a[j];
a[j]=i;
for (int k=1;k<=m;k++) ins(c[a[k]]);
a[j]=tmp;
if (cnt==m) trans[a[j]][i]=1;//x[a[j]]<x[i]
}
}
void work2(){
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++){
cnt=0; memset(num,0,sizeof(num));
int tmp=b[j];
b[j]=i;
for (int k=1;k<=m;k++) ins(c[b[k]]);
b[j]=tmp;
if (cnt==m) trans[i][b[j]]=1;//x[i]<x[b[j]]
}
}
void dfs(int u){
if (u>cnt){
ll sum=0;
for (int i=1;i<=n;i++)
if (vis[i]) sum+=1ll*(x[i]-v[i])*(x[i]-v[i]);
else{
int L=minv,R=maxv;
for (int j=1;j<=cnt;j++){
if (trans[pos[j]][i]) L=max(L,x[pos[j]]);
if (trans[i][pos[j]]) R=min(R,x[pos[j]]);
}
if (L>R){ sum=INF; break;}
if (v[i]<L) sum+=1ll*(L-v[i])*(L-v[i]);
if (v[i]>R) sum+=1ll*(v[i]-R)*(v[i]-R);
}
ans=min(ans,sum); return;
}
int L=minv,R=maxv;
for (int i=1;i<u;i++){
if (trans[pos[i]][pos[u]]) L=max(L,x[pos[i]]);
if (trans[pos[u]][pos[i]]) R=min(R,x[pos[i]]);
}
for (int i=L;i<=R;i++){ x[pos[u]]=i; dfs(u+1);}
}
int main(){
// freopen("shop.in","r",stdin);
// freopen("shop.out","w",stdout);
scanf("%d%d",&n,&m);
minv=1e6; maxv=0;
for (int i=1;i<=n;i++) scanf("%llu",&c[i]);
for (int i=1;i<=n;i++){
scanf("%d",&v[i]);
minv=min(minv,v[i]); maxv=max(maxv,v[i]);
}
for (int i=1;i<=m;i++){
scanf("%d",&a[i]);
vis[a[i]]=true;
}
for (int i=1;i<=m;i++){
scanf("%d",&b[i]);
vis[b[i]]=true;
}
work(); work2();
cnt=0;
for (int i=1;i<=n;i++)
if (vis[i]) pos[++cnt]=i;
if (cnt==m){
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (trans[i][j]) Union(i,j);
for (int i=1;i<=n;i++) vec[findset(i)].push_back(i);
ll sum,sum1,sum2,now; int tot;
for (int i=1;i<=n;i++){
sum=0; tot=0;
for (int j=0;j<(int)vec[i].size();j++) sum+=v[vec[i][j]],tot++;
if (tot){
now=(ll)(1.0*sum/tot);
sum1=0; for (int j=0;j<(int)vec[i].size();j++) sum1+=1ll*(now-v[vec[i][j]])*(now-v[vec[i][j]]);
now++;
sum2=0; for (int j=0;j<(int)vec[i].size();j++) sum2+=1ll*(now-v[vec[i][j]])*(now-v[vec[i][j]]);
ans+=min(sum1,sum2);
}
}
} else{
ans=INF; dfs(1);
}
printf("%lld\n",ans);
return 0;
}
100pts
- 这个做法要用到 \(30pts\) 建的图。(建图方法要优化一下)
- 我们把图建出来之后,原题就变成了一个保序回归问题。
- 由论文的知识,若要求每个物品的价格为 \(k/k+1\),这个问题的解。一定存在原问题的一
组最优解(每个物品的价格没有限制的最小回归代价)且可以通过向 \([k,k+1]\) 取整得到这个问题的最优解。 - 那么,我们求出每个物品价格为 \(k/k+1\) 时的答案,就能知道必有一组最优解,现在取 \(k\) 的物品最终价格都 \(\leq k\),取 \(k+1\) 的物品最终价格都 \(\geq k+1\)。
- 将两边分开递归计算答案,它们之间显然是相互不影响的。
- 取 \(k=mid=\frac{l+r}2\) 即可,用整体二分的思想,分成两部分,递归即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
typedef unsigned long long ull;
const ull INF=1ll<<60;
const int infdep=0x3f3f3f3f;
int n,m,a[1100],b[1100],fa[1100];
int v[1100],x[1100],minv,maxv;
bool trans[1100][1100],vis[1100];
ull c[1100],num[64]; ull ans[1100];
int cnt; int pos[1100];
vector<int> vec[1100],edge[1100];
namespace dinic{
int s,t,dep[1100]; ull F[310000];
int vis[1100];//是否到达过该点
int edgenum=1,V[310000],Next[310000],Head[1100];
void addedge(int u,int v,ull f){
V[++edgenum]=v; F[edgenum]=f;
Next[edgenum]=Head[u];
Head[u]=edgenum;
}
void link(int u,int v,ull f){
addedge(u,v,f);
addedge(v,u,0);
}
bool bfs(){
for (int i=1;i<=t;i++) vis[i]=false,dep[i]=infdep;
dep[s]=0;
vis[s]=true;
queue<int> que;
que.push(s);
while (!que.empty()){
int u=que.front(); que.pop();
vis[u]=false;
for (int e=Head[u];e;e=Next[e]){
int d=V[e];
if (dep[d]>dep[u]+1&&F[e]){
dep[d]=dep[u]+1;
if (!vis[d]){
que.push(d);
vis[d]=true;
}
}
}
}
return dep[t]<infdep;
}
ull dfs(int u,ull flow){
if (u==t){
vis[t]=true; //maxflow+=flow;
return flow;
}
ull used=0;
vis[u]=true;
for (int e=Head[u];e;e=Next[e]){
int d=V[e];
if ((!vis[d]||d==t)&&F[e]&&dep[d]==dep[u]+1){
int minflow=dfs(d,min(flow-used,F[e]));
if (minflow!=0) F[e]-=minflow,F[e^1]+=minflow,used+=minflow;
if (used==flow) break;
}
}
return used;
}
void maxflow(){
while (bfs()){
vis[t]=1;
while (vis[t]){
for (int i=1;i<=t;i++) vis[i]=false;
dfs(s,INF);
}
}
}
void init(int m){
s=m+1; t=m+2;
edgenum=1;
for (int i=1;i<=t;i++) Head[i]=0;
}
}
inline ull sqr(int x){
if (x<0) x=-x;
return (ull)x*x;
}
void ins(ull x){
for (int i=63;i>=0;i--)
if ((x>>i)&1){
if (!num[i]){ cnt++; num[i]=x; break;}
x^=num[i];
}
}
bool couldins(ull x){
for (int i=63;i>=0;i--)
if ((x>>i)&1){
if (!num[i]) return 1;
x^=num[i];
}
return 0;
}
void work(){
/*for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++){
if (a[j]==i) continue;
cnt=0; memset(num,0,sizeof(num));
int tmp=a[j];
a[j]=i;
for (int k=1;k<=m;k++) ins(c[a[k]]);
a[j]=tmp;
if (cnt==m) edge[i].push_back(a[j]); //x[a[j]]<x[i]
}*/
for (int j=1;j<=m;j++){
cnt=0; memset(num,0,sizeof(num));
for (int k=1;k<=m;k++)
if (k!=j) ins(c[a[k]]);
for (int i=1;i<=n;i++){
if (a[j]==i) continue;
if (cnt+couldins(c[i])==m) edge[i].push_back(a[j]); //x[a[j]]<x[i]
}
}
}
void work2(){
for (int j=1;j<=m;j++){
cnt=0; memset(num,0,sizeof(num));
for (int k=1;k<=m;k++)
if (k!=j) ins(c[b[k]]);
for (int i=1;i<=n;i++){
if (b[j]==i) continue;
if (cnt+couldins(c[i])==m) edge[b[j]].push_back(i); //x[i]<x[b[j]]
}
}
}
int p[1100],q[1100],id[1100];
void solve(int l,int r,int L,int R){ //x[a]<=x[b],\sum (x[i]-v[i])^2
if (l>r) return;
if (L==R){
for (int i=l;i<=r;i++) ans[p[i]]=L;
return;
}
int mid=(L+R)>>1;
dinic::init(r-l+1);
for (int i=l;i<=r;i++) id[p[i]]=i-l+1;
int u;
for (int i=l;i<=r;i++){
u=p[i];
if (v[u]>mid) dinic::link(i-l+1,dinic::t,sqr(v[u]-mid)-sqr(v[u]-mid-1));
else dinic::link(dinic::s,i-l+1,sqr(mid+1-v[u])-sqr(mid-v[u]));
for (int j:edge[u])
if (id[j]) dinic::link(i-l+1,id[j],INF);
}
for (int i=l;i<=r;i++) id[p[i]]=0;
dinic::maxflow();
int x=l,y=r;
for (int i=l;i<=r;i++)
if (dinic::dep[i-l+1]!=infdep) q[x++]=p[i];
else q[y--]=p[i];
for (int i=l;i<=r;i++) p[i]=q[i];
solve(l,x-1,L,mid); solve(y+1,r,mid+1,R);
}
int main(){
// freopen("shop.in","r",stdin);
// freopen("shop.out","w",stdout);
scanf("%d%d",&n,&m);
minv=1e6; maxv=0;
for (int i=1;i<=n;i++) scanf("%llu",&c[i]);
for (int i=1;i<=n;i++){
scanf("%d",&v[i]);
minv=min(minv,v[i]); maxv=max(maxv,v[i]);
}
for (int i=1;i<=m;i++){
scanf("%d",&a[i]);
vis[a[i]]=true;
}
for (int i=1;i<=m;i++){
scanf("%d",&b[i]);
vis[b[i]]=true;
}
work(); work2();
for (int i=1;i<=n;i++) p[i]=i;
solve(1,n,minv,maxv);
ull sum=0;
for (int i=1;i<=n;i++) sum+=sqr(v[i]-ans[i]);
printf("%llu\n",sum);
return 0;
}
Day2T1
- 状压dp即可,将答案按系数分开计算。
- 为了节省空间,求出入边的数量需要由一种奇怪的方法求,详见代码。
#include<cstdio>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
int n,m,k,SL[110],SR[110],f[8500000];
int cnt[110][110],Log2[8500000],tot[8500000];
int top,st[8500000],L[110][110],R[110][110];
int main(){
scanf("%d%d%d",&n,&m,&k);
int pre,now;
for (int i=1;i<=n;i++,pre=now){
scanf("%d",&now); now--;
if (i>1&&now!=pre){ cnt[pre][now]++; SL[pre]++; SR[now]++;}
}
int upperlim=(1<<m)-1; f[0]=0;
for (int i=1;i<=upperlim;i++) f[i]=INF;
for (int i=0;i<m;i++) Log2[1<<i]=i;
int x;
for (int s=0;s<=upperlim;s++){
if (s){
while (top&&(st[top]&s)!=st[top]) top--;
x=Log2[s&-s]; st[++top]=s;
for (int i=0;i<m;i++){
L[top][i]=L[top-1][i]+cnt[i][x];
R[top][i]=R[top-1][i]+cnt[x][i];
}
}
//L,R算出的为i到s的边数与s到i的边数
tot[s]=tot[s>>1]+(s&1); x=tot[s]+1;
for (int i=0;i<m;i++)
if (!(s&(1<<i)))
f[s|(1<<i)]=min(f[s|(1<<i)],f[s]+x*(L[top][i]*k+R[top][i]-(SL[i]-L[top][i])+(SR[i]-R[top][i])*k));
}
printf("%d\n",f[upperlim]);
return 0;
}
Day2T2
- 只需要实现一个数据结构维护一个无序数集,支持全部+1、插入一个数、合并、查询异或和。把二进制位反过来建\(\texttt{01-Trie}\)即可。
- \(\texttt{01-Trie}\) 合并的复杂度等同于线段树合并的复杂度。
#include<cstdio>
#include<algorithm>
using namespace std;
int n,val[530000],ch[21000000][2],tot[21000000],rt[530000],cnt;
int edgenum=1,vet[530000],Next[530000],Head[530000];
long long ans;
void addedge(int u,int v){
vet[++edgenum]=v;
Next[edgenum]=Head[u];
Head[u]=edgenum;
}
void insert(int &x,int v,int d){
if (!x) x=++cnt;
tot[x]^=1;
if (d>20) return;
insert(ch[x][(v>>d)&1],v,d+1);
}
int merge(int x,int y){
if (!x||!y) return x|y;
tot[x]^=tot[y];
ch[x][0]=merge(ch[x][0],ch[y][0]);
ch[x][1]=merge(ch[x][1],ch[y][1]);
return x;
}
void dfs(int u){
insert(rt[u],val[u],0);
int v;
for (int e=Head[u];e;e=Next[e]){
v=vet[e]; dfs(v);
int x=rt[v];
for (int i=0;i<=20;i++){
val[u]^=tot[x]<<i;
swap(ch[x][0],ch[x][1]);
x=ch[x][0];
}
rt[u]=merge(rt[u],rt[v]);
val[u]^=val[v];
}
ans+=val[u];
}
int main(){
scanf("%d",&n); int f;
for (int i=1;i<=n;i++) scanf("%d",&val[i]);
for (int i=2;i<=n;i++){
scanf("%d",&f);
addedge(f,i);
}
dfs(1);
printf("%lld\n",ans);
return 0;
}
Day2T3
- 求所有生成树边权和:令每条边边权为 \(1+w_ix\) ,然后在模\(x^2\)下做普通矩阵树即可,时间复杂度\(O(n^3)\)。
- 莫比乌斯反演,可以发现答案为\(\sum_d\varphi(d)\times [\mbox{所有边都是}d\mbox{的倍数的生成树的边权和}]\),看上去需要做\(\max\{w_i\}\)次求行列式,但考虑到参与构成此部分的图中边的总数不超过 \(144m\),而一张少于\(n-1\)条边的图中一定不包含生成树,故求行列式的次数其实只有\(O(\frac{144m}{n-1})\)次,总复杂度可以估计为\(O(144n^4)\)。
#include<cstdio>
#include<utility>
#include<cstring>
using namespace std;
const int Mod=998244353;
int n,m,ans,u[1100],v[1100],w[1100]; int tot[210000];
int cnt,phi[210000],p[210000]; bool vis[210000];
struct node{
int a,b;
node(int x=0,int y=0){ a=x; b=y;}
node operator+(const node &x) const{ return node((a+x.a)%Mod,(b+x.b)%Mod);}
node operator-(const node &x) const{ return node((a-x.a+Mod)%Mod,(b-x.b+Mod)%Mod);}
node operator*(const node &x) const{ return node(1ll*a*x.a%Mod,(1ll*a*x.b+1ll*b*x.a)%Mod);}
} A[32][32];
int qpow(int x,int a){
int res=1;
while (a){
if (a&1) res=1ll*res*x%Mod;
x=1ll*x*x%Mod; a>>=1;
}
return res;
}
node getinv(const node x){
int inv=qpow(x.a,Mod-2);
return node(inv,1ll*(Mod-x.b)%Mod*inv%Mod*inv%Mod);
}
void init(int n){
phi[1]=1; vis[1]=true;
for (int i=2;i<=n;i++){
if (!vis[i]) p[++cnt]=i,phi[i]=i-1;
for (int j=1;i*p[j]<=n;j++){
vis[i*p[j]]=true;
if (i%p[j]==0){
phi[i*p[j]]=phi[i]*p[j];
break;
}
phi[i*p[j]]=phi[i]*(p[j]-1);
}
}
}
int gauss(int n){
node res=node(1,0); bool rev=false;
for (int i=1;i<=n;i++){
if (!A[i][i].a){
for (int j=i+1;j<=n;j++){
if (A[j][i].a){
rev=!rev;
swap(A[i],A[j]);
break;
}
}
}
node inv=getinv(A[i][i]);
for (int j=i+1;j<=n;j++){
node tmp=A[j][i]*inv;
for (int k=i;k<=n;k++) A[j][k]=A[j][k]-tmp*A[i][k];
}
res=res*A[i][i];
}
if (rev) return (Mod-res.b)%Mod;
else return res.b;
}
int getans(int val){
memset(A,0,sizeof(A));
for (int i=1;i<=m;i++){
if (w[i]%val) continue;
A[u[i]][u[i]]=A[u[i]][u[i]]+node(1,w[i]);
A[v[i]][v[i]]=A[v[i]][v[i]]+node(1,w[i]);
A[u[i]][v[i]]=A[u[i]][v[i]]-node(1,w[i]);
A[v[i]][u[i]]=A[v[i]][u[i]]-node(1,w[i]);
}
return gauss(n-1);
}
void gettot(int x){
for (int i=1;i*i<=x;i++){
if (x%i==0){
tot[i]++;
if (i*i!=x) tot[x/i]++;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
scanf("%d%d%d",&u[i],&v[i],&w[i]);
gettot(w[i]);
}
init(152501);
for (int i=1;i<=152501;i++){
if (tot[i]<n-1) continue;
ans=(ans+1ll*phi[i]*getans(i))%Mod;
}
printf("%d\n",ans);
return 0;
}