NOI2020联合省选A卷题解
Day1
T1 冰火战士
不难发现随着温度增加能用的冰战士越来越多,火战士越来越少
于是只需要求能用的冰战士的总能量比火战士少的最大温度和冰战士的总能量比火战士多的最小温度即可
于是离散化+线段树上二分解决
最后输出温度要输出从当前求出的最优温度开始第一个有火战士的温度
时间复杂度是\(O(nlogn)\)
下面是考场上的大常数代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
template <class T> inline void read(T &n){
T w=0,t=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') t=-1;ch=getchar();}
while(isdigit(ch)){w=(w<<3)+(w<<1)+(ch^48);ch=getchar();}
n=w*t;
}
const int maxn=2000005;
int q,b[maxn],N;
int now1,now2;
struct query{
int opt,k,x,y;
}Q[maxn];
int sum[2][maxn<<2];
void modify(int l,int r,int id,int x,int y,int opt){
sum[opt][id]+=y;
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) modify(l,mid,id<<1,x,y,opt);
else modify(mid+1,r,id<<1|1,x,y,opt);
}
int num[2][maxn];
int ans,pos;
void query1(int l,int r,int id,int cnt1,int cnt2){//0<=1 last
if(l==r){
cnt1+=sum[0][id];
int res=min(cnt1,now2-cnt2);
if(!res) return;
if(res*2>=ans) ans=res*2,pos=max(pos,l);
return;
}
int mid=(l+r)>>1;
if(cnt1+sum[0][id<<1]+num[0][mid+1]<=now2-cnt2-sum[1][id<<1]) query1(mid+1,r,id<<1|1,cnt1+sum[0][id<<1],cnt2+sum[1][id<<1]);
else query1(l,mid,id<<1,cnt1,cnt2);
}
int getnxt(int l,int r,int id,int x){
if(!sum[1][id]) return 0;
if(l==r) return l;
int mid=(l+r)>>1;
if(x>mid) return getnxt(mid+1,r,id<<1|1,x);
else{
int res=getnxt(l,mid,id<<1,x);
if(res) return res;
return getnxt(mid+1,r,id<<1|1,x);
}
}
void query2(int l,int r,int id,int cnt1,int cnt2){//0>=1 first
if(l==r){
cnt1+=sum[0][id];
int res=min(cnt1,now2-cnt2);
if(!res) return;
if(res*2>=ans) ans=res*2,pos=max(pos,l);
return;
}
int mid=(l+r)>>1;
if(cnt1+sum[0][id<<1]>=now2-cnt2-sum[1][id<<1]+num[1][mid]) query2(l,mid,id<<1,cnt1,cnt2);
else query2(mid+1,r,id<<1|1,cnt1+sum[0][id<<1],cnt2+sum[1][id<<1]);
}
void getans(){
if(!now1||!now2){
puts("Peace");
return;
}
ans=pos=0;
query1(1,N,1,0,0);
query2(1,N,1,0,0);
if(!ans){
puts("Peace");
return;
}
printf("%d %d\n",b[getnxt(1,N,1,pos)],ans);
}
int main(){
read(q);
for(int i=1;i<=q;i++){
read(Q[i].opt);
if(Q[i].opt==1){
read(Q[i].k),read(Q[i].x),read(Q[i].y);
b[++N]=Q[i].x;
}
else read(Q[i].k);
}
sort(b+1,b+N+1);
N=unique(b+1,b+N+1)-(b+1);
for(int i=1;i<=q;i++)
if(Q[i].opt==1) Q[i].x=lower_bound(b+1,b+N+1,Q[i].x)-b;
for(int i=1;i<=q;i++){
if(Q[i].opt==1){
if(Q[i].k==0){
now1+=Q[i].y;
num[0][Q[i].x]+=Q[i].y;
modify(1,N,1,Q[i].x,Q[i].y,0);
}
else{
now2+=Q[i].y;
num[1][Q[i].x]+=Q[i].y;
modify(1,N,1,Q[i].x,Q[i].y,1);
}
getans();
}
if(Q[i].opt==2){
int tmp=Q[i].k;
if(Q[tmp].k==0){
now1-=Q[tmp].y;
num[0][Q[tmp].x]-=Q[tmp].y;
modify(1,N,1,Q[tmp].x,-Q[tmp].y,0);
}
else{
now2-=Q[tmp].y;
num[1][Q[tmp].x]-=Q[tmp].y;
modify(1,N,1,Q[tmp].x,-Q[tmp].y,1);
}
getans();
}
}
return 0;
}
T2 组合数问题
拆开多项式发现是求
\(\sum_{i=0}^m a_i* \sum_{k=0}^n (^n_k)*x^k * k^i\)
考虑求\(\sum_{k=0}^n (^n_k)*x^k * k^i\)
这个套路与CF932E相同
先考虑
\(f(x)=\sum_{k=0}^n (^n_k)*x^k =(x+1)^n\)
注意到
\(f'(x)=\sum_{k=1}^n (^n_k)*x^{k-1}*k\)
\(f'(x)*x=\sum_{k=1}^n (^n_k)*x^k*k=\sum_{k=0}^n (^n_k)*x^k*k\)
于是我们惊人的发现
设
\(f_t(x)=\sum_{k=0}^n (^n_k)*x^k*k^t\)
则
\(f_t(x)=f_{t-1}'(x)*x\)
而
\([(x+1)^n]'*x=n*(x+1)^{n-1}*x=n*(x+1)^n-n*(x+1)^{n-1}\)
于是每个\(f_t(x)\)都可以用\((x+1)\)的幂次线性组合出来
我们每次暴力求导模拟即可算出
\(i=0-m\)的\(f_i(x)\)
而
\(\sum_{i=0}^m a_i* \sum_{k=0}^n (^n_k)*x^k * k^i=\sum_{i=0}^m a_i*f_i(x)\)
时间复杂度是\(O(m^2)\)
可以通过此题
下面是考场上的大常数代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
template <class T> inline void read(T &n){
T w=0,t=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') t=-1;ch=getchar();}
while(isdigit(ch)){w=(w<<3)+(w<<1)+(ch^48);ch=getchar();}
n=w*t;
}
const int maxn=1005;
int n,x,m,mod,a[maxn];
inline int add(int x,int y){
x+=y;return x>=mod?x-mod:x;
}
inline int mul(int x,int y){return 1ll*x*y%mod;}
int quick_pow(int x,int y){
int res=1;
while(y){
if(y&1) res=mul(res,x);
x=mul(x,x),y>>=1;
}
return res;
}
int cnt[maxn],pw[maxn];
int main(){
read(n),read(x),read(mod),read(m);
for(int i=0;i<=m;i++) read(a[i]);
for(int i=0;i<=m;i++) pw[i]=quick_pow(x+1,n-i);
int ans=mul(a[0],pw[0]);
cnt[0]=1;
for(int i=1;i<=m;i++){
for(int j=i-1;j>=0;j--){
cnt[j+1]=add(cnt[j+1],mod-mul(n-j,cnt[j]));
cnt[j]=mul(cnt[j],n-j);
}
int res=0;
for(int j=0;j<=i;j++) res=add(res,mul(cnt[j],pw[j]));
ans=add(ans,mul(a[i],res));
}
cout<<ans<<endl;
return 0;
}
T3 魔法商店
猜结论/利用拟阵推导可知
只需满足
用A外的一个元素替换A中的一个元素使得新集合合法并满足新集合的权值大于等于原A集合的权值
用B外的一个元素替换B中的一个元素使得新集合合法并满足新集合的权值小于等于原B集合的权值
这其实就是一些元素权值间的大小关系
使用线性基建图即可
以A举例,存一下线性基中的每一个元素是由哪些A中的元素异或得来
对于A外的每一个元素,在线性基里查到它是由A中的哪些元素异或得来即可
接下来就是一个经典的保序回归\(L_2\)问题
根据论文可知使用整体二分+网络流即可
时间复杂度大概是\(O(nm+logn*maxflow(n,nm))\)
下面是考后写的大常数代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
char buf[1<<20],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
template<class T> inline void read(T &n){
char ch=GC;T w=1,x=0;
while(!isdigit(ch)){if(ch=='-') w=-1;ch=GC;}
while(isdigit(ch)){x=(x<<3)+(x<<1)+(ch^48);ch=GC;}
n=x*w;
}
const int maxm=64;
const int maxn=1005;
ull c[maxn];
int n,m,v[maxn],a[maxn],b[maxn],bela[maxn],belb[maxn];
struct lb{
ull b[maxm],id[maxm];
void insert(ull x,int nw){
ull pos=(1ull<<nw);
for(int i=maxm-1;i>=0;i--){
if((x>>i)&1){
if(!b[i]){
b[i]=x,id[i]=pos;
return;
}
x^=b[i],pos^=id[i];
}
}
}
ull find(ull x){
ull res=0;
for(int i=maxm-1;i>=0;i--)
if((x>>i)&1) x^=b[i],res^=id[i];
return res;
}
};
lb A,B;
ll ans;
const ll inf=0x3f3f3f3f3f3f3f3f;
namespace flow{
int st,ed=n+1,cnt=1;
int use[maxn],tot;
int last[maxn],Next[maxn<<8],to[maxn<<8];
ll w[maxn<<8];
inline void clear(){
last[st]=last[ed]=0;
for(int i=1;i<=tot;i++) last[use[i]]=0;
memset(last,0,sizeof(last)),cnt=1,tot=0;
}
inline void addedge(int x,int y,ll z){
Next[++cnt]=last[x],last[x]=cnt;
to[cnt]=y,w[cnt]=z;
}
inline void add(int x,int y,ll z){
addedge(x,y,z),addedge(y,x,0);
}
inline void addu(int x){use[++tot]=x;}
int dep[maxn],cur[maxn];
queue <int> Q;
bool bfs(){
dep[ed]=0;
for(int i=1;i<=tot;i++) dep[use[i]]=0;
dep[st]=1,Q.push(st);
while(!Q.empty()){
int u=Q.front();Q.pop();
for(int i=last[u];i;i=Next[i]){
int v=to[i];
if(w[i]&&!dep[v]){
dep[v]=dep[u]+1;
Q.push(v);
}
}
}
return dep[ed];
}
ll dfs(int u,ll low){
if(u==ed) return low;
ll add=0;
for(int i=cur[u];i&&add<low;i=Next[i]){
int v=to[i];cur[u]=i;
if(w[i]&&dep[v]==dep[u]+1){
ll tmp=dfs(v,min(low-add,w[i]));
w[i]-=tmp,w[i^1]+=tmp;
add+=tmp;
}
}
return add;
}
ll dinic(){
ll res=0;
while(bfs()){
cur[st]=last[st],cur[ed]=last[ed];
for(int i=1;i<=tot;i++) cur[use[i]]=last[use[i]];
res+=dfs(st,inf);
}
return res;
}
}
using flow::st;
using flow::ed;
using flow::dep;
using flow::add;
inline ll sqr(ll x){return 1ll*x*x;}
int bel[maxn];
void solve(int l,int r,vector <int> P,vector <pair<int,int>> E){
if(l==r||!P.size()){
for(auto u:P) ans+=sqr(v[u]-l);
return;
}
int mid=(l+r)>>1;
//mid mid+1
flow::clear();
for(auto u:E) add(u.first,u.second,inf);
for(auto u:P){
flow::addu(u);
if(v[u]<=mid) add(u,ed,sqr(mid+1-v[u])-sqr(mid-v[u]));
else add(st,u,sqr(v[u]-mid)-sqr(v[u]-mid-1));
}
flow::dinic();
vector <int> pl,pr;
vector <pair<int,int>> el,er;
for(auto u:P){
if(!dep[u]) pl.push_back(u),bel[u]=0;
else pr.push_back(u),bel[u]=1;
}
for(auto u:E){
int x=u.first,y=u.second;
if(bel[x]!=bel[y]) continue;
if(!bel[x]) el.push_back(u);
else er.push_back(u);
}
solve(l,mid,pl,el);
solve(mid+1,r,pr,er);
}
vector <int> P;
vector <pair<int,int>> E;
int main(){
read(n),read(m),ed=n+1;
int Min=1e9,Max=0;
for(int i=1;i<=n;i++) read(c[i]);
for(int i=1;i<=n;i++) read(v[i]),Min=min(Min,v[i]),Max=max(Max,v[i]);
for(int i=1;i<=m;i++){
read(a[i]),bela[a[i]]=1;
A.insert(c[a[i]],i-1);
}
for(int i=1;i<=m;i++){
read(b[i]),belb[b[i]]=1;
B.insert(c[b[i]],i-1);
}
for(int i=1;i<=n;i++){
if(!bela[i]){
ull tmp=A.find(c[i]);
for(int j=m-1;j>=0;j--)
if((tmp>>j)&1) E.push_back(make_pair(a[j+1],i));
}
if(!belb[i]){
ull tmp=B.find(c[i]);
for(int j=m-1;j>=0;j--)
if((tmp>>j)&1) E.push_back(make_pair(i,b[j+1]));
}
}
for(int i=1;i<=n;i++) P.push_back(i);
solve(Min,Max,P,E);
cout<<ans<<endl;
return 0;
}
Day2
T1 信号传递
不难发现两点间距离的贡献可以放在两个点上分别计算
那么在排列的第\(i\)个位置加入\(j\)号点的贡献就只跟排列前面已经加入哪些点有关
考虑状压dp
设\(f[i][j]\) 表示当前已经加入了排列中前\(i\)个位置,已经加入的状态为\(j\)的最小贡献
转移可以枚举第i个位置是哪个点计算贡献
直接搞时间是\(O(m^3*2^m)\)的,考虑优化
首先\(i\)这一维可以由\(j\)推出
那么dp数组省掉一维,时间优化到\(O(m^2*2^m)\)
注意到我们每次枚举了第\(i\)个数后要枚举其它所有数计算贡献
这一步考虑预处理
设\(g[i][j]\)表示\(i\)号点对当前状态为\(j\)的贡献
转移可以从\(g[i][j-lowbit(j)]\)转移过来
这样时空复杂度为\(O(m*2^m)\)
注意到毒瘤出题人卡空间
而\(g\)数组需要使用约\(768M\)的空间
但是\(i\)号点只会对除了\(i\)号点外的m-1个元素的状态产生贡献
于是处理\(g\)只需要\(O(m*2^{m-1})\)的时空复杂度
再用\(lowbit\)转移\(f\)即可做到总\(O(m*2^{m-1})\)的时空复杂度
下面是考场上的大常数代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
template <class T> inline void read(T &n){
T w=0,t=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')t=-1;ch=getchar();}
while(isdigit(ch)){w=(w<<3)+(w<<1)+(ch^48);ch=getchar();}
n=w*t;
}
const int maxm=23;
const int maxn=100005;
int n,m,K,a[maxn],cnt[maxm+5][maxm+5],g[maxm][1<<22];
char c[1<<maxm],Log[1<<maxm];
ll f[1<<maxm];
inline int lowbit(int x){return x&(-x);}
int main(){
read(n),read(m),read(K);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<n;i++){
cnt[a[i]][a[i+1]]++;
if(a[i]==a[i+1]) continue;
g[a[i]-1][0]--,g[a[i+1]-1][0]+=K;
}
int N=(1<<m)-1;
Log[0]=-1;
for(int i=1;i<=N;i++) c[i]=c[i>>1]+(i&1),Log[i]=Log[i>>1]+1;
memset(f,0x3f,sizeof(f));
f[0]=0;
int S=(1<<(m-1))-1;
for(int i=0;i<m;i++)
for(int j=1;j<=S;j++){
int k=lowbit(j),l=Log[k];
if(l>=i) l++;
g[i][j]=g[i][j-k]-1ll*(K-1)*cnt[l+1][i+1]+1ll*(K+1)*cnt[i+1][l+1];
}
for(int j=1;j<=N;j++){
int i=c[j];
for(int x=j;x;x-=lowbit(x)){
int k=Log[lowbit(x)];
int now=(j^(1<<k));
int tmp=((now>>(k+1))<<k);
tmp+=now&((1<<k)-1);
f[j]=min(f[j],f[now]+1ll*i*g[k][tmp]);
}
}
cout<<f[N]<<endl;
return 0;
}
T2 树
这个题看似又+1又xor
但其实只需要一个trick
考虑对所有树建立01trie树
但这里从低位向高位建
一个点的信息传到父亲那里要+1
考虑在这样的01trie树上进位
不难发现每次就是交换当前点的左右儿子
然后对于交换后的\(ch[x][0]\)继续这个过程
同时维护每个节点的\(size\)即可算出答案
对于一次操作时间复杂度是\(O(logn)\)的
再结合树上trie树合并
时间复杂度是\(O(nlogn)\)
下面是考后写的大常数代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
char buf[1<<20],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
template<class T> inline void read(T &n){
char ch=GC;T w=1,x=0;
while(!isdigit(ch)){if(ch=='-') w=-1;ch=GC;}
while(isdigit(ch)){x=(x<<3)+(x<<1)+(ch^48);ch=GC;}
n=x*w;
}
const int maxn=525015;
int n,v[maxn];
int last[maxn],Next[maxn],to[maxn],cnt;
inline void addedge(int x,int y){
Next[++cnt]=last[x];
last[x]=cnt,to[cnt]=y;
}
int root[maxn],ch[maxn*22][2],st[maxn*22],top,tot;
int size[maxn*22],val[maxn*22];
inline int newnode(){
int rt=top?st[top--]:++tot;
ch[rt][0]=ch[rt][1]=size[rt]=val[rt]=0;
return rt;
}
inline void pushup(int rt,int d){
size[rt]=size[ch[rt][0]]+size[ch[rt][1]];
val[rt]=val[ch[rt][0]]^val[ch[rt][1]];
val[rt]^=((size[ch[rt][1]]&1)<<d);
}
void insert(int &rt,int x,int d){
if(!rt) rt=newnode();
if(d==21){
size[rt]++;
return;
}
if((x>>d)&1) insert(ch[rt][1],x,d+1);
else insert(ch[rt][0],x,d+1);
pushup(rt,d);
}
int merge(int x,int y){
if(!x||!y) return x+y;
int z=newnode();
size[z]=size[x]+size[y];
val[z]=val[x]^val[y];
ch[z][0]=merge(ch[x][0],ch[y][0]);
ch[z][1]=merge(ch[x][1],ch[y][1]);
st[++top]=x,st[++top]=y;
return z;
}
void add(int rt,int d){
swap(ch[rt][0],ch[rt][1]);
if(ch[rt][0]) add(ch[rt][0],d+1);
pushup(rt,d);
}
ll ans;
void dfs(int x){
for(int i=last[x];i;i=Next[i]){
dfs(to[i]);
root[x]=merge(root[x],root[to[i]]);
}
add(root[x],0);
insert(root[x],v[x],0);
ans+=val[root[x]];
}
int main(){
read(n);
for(int i=1;i<=n;i++) read(v[i]);
for(int i=2,x;i<=n;i++) read(x),addedge(x,i);
dfs(1);
cout<<ans<<endl;
return 0;
}
T3 作业题
求边权\(gcd\)为\(x\)的生成树个数可以用莫比乌斯反演+矩阵树定理解决
这个套路是luogu3790
但这里要求生成树边权和\(*gcd\)
但是变元矩阵树只能求边权之积
考虑将矩阵的节点记为一个pair(a,b)
表示(所有方案的边权总和,方案总数)
\((a,b)+(c,d)\) 记为 \((a+b,c+d)\)
\((a,b)*(c,d)\) 记为 \((a*d + b*c,b*d)\)
高斯消元求到最后答案的第一维就是边权和
证明可以考虑矩阵树定理本身的意义
下面是考场上的大常数代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
template <class T> inline void read(T &n){
T w=0,t=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')t=-1;ch=getchar();}
while(isdigit(ch)){w=(w<<3)+(w<<1)+(ch^48);ch=getchar();}
n=w*t;
}
const int maxn=31;
const int mod=998244353;
inline int add(int x,int y){
x+=y;return x>=mod?x-mod:x;
}
inline int mul(int x,int y){
return 1ll*x*y%mod;
}
int quick_pow(int x,int y){
int res=1;
while(y){
if(y&1) res=mul(res,x);
x=mul(x,x),y>>=1;
}
return res;
}
inline int getinv(int x){
return quick_pow(x,mod-2);
}
struct data{
int x,y;
data(int _x=0,int _y=0) {x=_x,y=_y;}
friend data operator +(data a,data b){
return data(add(a.x,b.x),add(a.y,b.y));
}
friend data operator -(data a,data b){
return data(add(a.x,mod-b.x),add(a.y,mod-b.y));
}
friend data operator *(data a,data b){
return data(add(mul(a.x,b.y),mul(a.y,b.x)),mul(a.y,b.y));
}
friend data operator /(data a,data b){
int tmp=mul(a.y,getinv(b.y));
int tp=add(a.x,mod-mul(b.x,tmp));
return data(mul(tp,getinv(b.y)),tmp);
}
};
data F[maxn][maxn];
data Gauss(int N){
data res(0,1);
for(int i=1;i<=N;i++){
if(!F[i][i].y){
int k=0;
for(int j=i+1;j<=N;j++)
if(F[j][i].y){
k=j;break;
}
if(!k) return 0;
for(int j=i;j<=N;j++) swap(F[i][j],F[k][j]);
res=res*data(mod-1,mod-1);
}
res=res*F[i][i];
for(int j=i+1;j<=N;j++){
data tmp=F[j][i]/F[i][i];
for(int k=i;k<=N;k++) F[j][k]=F[j][k]-(F[i][k]*tmp);
}
}
return res;
}
int n,m;
struct edge{
int x,y,z;
edge(int _x=0,int _y=0,int _z=0){x=_x,y=_y,z=_z;}
};
const int maxm=152501;
vector <edge> E[maxm+5];
int isp[maxm+5],pri[maxm+5],tot,mu[maxm+5];
void init(){
mu[1]=1;
for(int i=2;i<=maxm;i++){
if(!isp[i]) pri[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&i*pri[j]<=maxm;j++){
isp[i*pri[j]]=1;
if(i%pri[j]==0) break;
mu[i*pri[j]]=-mu[i];
}
}
}
int res[maxm+5],cnt[maxm+5];
int main(){
read(n),read(m),init();
int Max=0;
for(int i=1,x,y,z;i<=m;i++){
read(x),read(y),read(z);
Max=max(Max,z);
int d=floor(sqrt(z));
for(int j=1;j<=d;j++){
if(z%j) continue;
E[j].push_back(edge(x,y,z));
if(z/j!=j) E[z/j].push_back(edge(x,y,z));
}
}
for(int i=1;i<=Max;i++){
if(E[i].size()<n-1) continue;
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++) F[j][k]=data();
for(int j=0;j<(int)E[i].size();j++){
int x=E[i][j].x,y=E[i][j].y,z=E[i][j].z;
F[x][x]=F[x][x]+data(z,1);
F[y][y]=F[y][y]+data(z,1);
F[x][y]=F[x][y]-data(z,1);
F[y][x]=F[y][x]-data(z,1);
}
res[i]=Gauss(n-1).x;
}
int ans=0;
for(int i=1;i<=Max;i++){
for(int j=i;j<=Max;j+=i) cnt[i]=add(cnt[i],mul(add(mu[j/i],mod),res[j]));
ans=add(ans,mul(cnt[i],i));
}
cout<<ans<<endl;
return 0;
}