要活下去,总有一天我们能笑着缅怀过去的艰辛。
T1.zoo
找最小值的最大值的和,很绕口,但是这个让我联想起了货车运输,正是求图上两点路径最小值的最大值,做法自然是用最大生成树上找最小边的方法去卡这个限制。
那就好说了,按两点最小权值建边,建图,建树,然后呢???
我这里写了50分的O(n^2)暴力,对每个点来一次dfs统计,走人了。
正解提供了这样一种似曾相识的思路,学到了。
考虑Kruskal中合并两个联通块的情况,由于边是从大往小枚举的,这条边一定是连接这两个联通块的“瓶颈”边,也就是最小边,所以答案就要加上2*siz[u]*siz[v]*w
这就完啦,学会了一种神奇的思路。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int rd(){
int ret=0,f=1;char c;
while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
while(isdigit(c))ret=ret*10+c-'0',c=getchar();
return ret*f;
}
const int MAXN=1000005;
struct Edge{
int from,to;
long long w;
}e[MAXN<<1];
int ecnt;
inline void add(int x,int y,int w){
e[++ecnt].from = x;
e[ecnt].to = y;
e[ecnt].w = w;
}
int n,m;
long long val[MAXN];
int fa[MAXN];
int fnd(int x){
return x==fa[x]?x:fa[x]=fnd(fa[x]);
}
void cat(int x,int y){
fa[y]=x;
}
bool cmp(const Edge &x,const Edge &y){
return x.w>y.w;
}
unsigned long long siz[MAXN];
unsigned long long ans;
int main(){
n=rd();m=rd();
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1,val[i]=rd();
int x,y,w;
for(int i=1;i<=m;i++){
x=rd();y=rd();w=min(val[x],val[y]);
add(x,y,w);
}
sort(e+1,e+1+ecnt,cmp);
int cnt=0,nx,ny;
for(int i=1;i<=ecnt;i++){
x=e[i].from,y=e[i].to;w=e[i].w;
nx=fnd(x),ny=fnd(y);
if(nx==ny) continue;
ans+=siz[nx]*siz[ny]*1ll*e[i].w;
siz[nx]+=siz[ny];
cat(nx,ny);
if(++cnt==n-1) break;
}
cout<<(ans<<1);
return 0;
}
T2.segment
考场没什么思路,磕了半天第三题,十分钟写的30分O(n^2)暴力
正解是这样考虑区间覆盖的,对于一个区间[u,v],假设[l,r]能覆盖它,那必定有l<=u&&v<=r
那么我们可以开两个树状数组,分别记录左端点和右端点,这两个树状数组以权值为下表,可以方便地查出前缀和
两个前缀和相减就是答案啦
对于删除操作,只需要把对应位置-1。
这根本不segment tree...考场上第一反应居然是主席树版李超线段树?啥东西..

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN=1000005;
int n,T;
inline int rd(){
int ret=0,f=1;char c;
while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
while(isdigit(c))ret=ret*10+c-'0',c=getchar();
return ret*f;
}
struct BIT{
int t[MAXN];
void init(){memset(t,0,sizeof(t));}
inline void update(int x,int w){for(int i=x;i<=n;i+=i&-i) t[i]+=w;}
inline int query(int x){int ret=0;for(int i=x;i;i-=i&-i)ret+=t[i];return ret;}
};
int savx[MAXN],savy[MAXN],opt[MAXN];
int bx[MAXN],by[MAXN];
BIT L,R;
int id[MAXN];
void solve(){
L.init();R.init();
memset(id,0,sizeof(id));
n=rd();
int sum=0,tmp;
for(int i=1;i<=n;i++){
opt[i]=rd();
if(!opt[i]){bx[i]=savx[i]=rd();by[i]=savy[i]=bx[i]+(++sum);id[sum]=i;}
else{tmp=rd();tmp=id[tmp];bx[i]=savx[i]=savx[tmp];by[i]=savy[i]=savy[tmp];}
}
sort(bx+1,bx+1+n);sort(by+1,by+1+n);
int totx=unique(bx+1,bx+1+n)-bx,toty=unique(by+1,by+1+n)-by;
for(int i=1;i<=n;i++){
savx[i]=lower_bound(bx+1,bx+1+totx,savx[i])-bx;//%%%HSZ
savy[i]=lower_bound(by+1,by+1+toty,savy[i])-by;
}
for(int i=1;i<=n;i++){
if(opt[i]){L.update(savx[i],-1);R.update(savy[i],-1);continue;}
printf("%d\n",R.query(savy[i])-L.query(savx[i]-1));
L.update(savx[i],1);R.update(savy[i],1);
}
}
int main(){
T=rd();int t=T;
while(T--) printf("Case #%d:\n",t-T),solve();
return 0;
}
T3.number
考虑这样一个事情,k严格小于max(n,m),意味着,不存在一行或者一列,使得该行或该列全部被预先填好。
也就是说,每一行、列都会有至少一个空着的,可以考虑这样一个事情,在k=0的情况下:
对于第一列,共有n个元素,要填入奇数个-1,有C(n,1)+C(n,3)+C(n,5)+...种填法,SPLI学长讲过,这货就等于2^(n-1)
同理,每列亦如此,答案就是(n-1)*(m-1),因为有一列是空出来的。
在考虑k的情况下,会发现,填入一个 k,答案就会除以二,所以答案就是(n-1)*(m-1)-k。
为什么这样对呢?因为之前说过,k严格小于max(n,m)
注意几个事情,当k>(n-1)*(m-1)时,要取0,也就是答案是pow(2,max(0,(n-1)*(m-1)-k)),以及当(n+m)为奇数,也就是(n&1)!=(m&1)时
考试的时候推出来式子,怕不稳就对着30写了个状压,大概这样
f[i][j]表示考虑到第i行,每列在前i行共填入了奇数/偶数个-1,用0和1表示
考虑转移,枚举i-1行和i行的状态j和k,填写了-1的位置就是v=j^k,判断这个v中是否是奇数个1(满足每行的限制),即可转移
问题是没仔细考虑空间!MLE
吸取教训,吸取教训。(其实写的那个式子没考虑和0取max,测出来也只有60分罢了)

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,k,MOD;
typedef long long ll;
ll qpow(ll x,ll y){
ll ret=1,base=x;
while(y){
if(y&1)(ret*=base)%=MOD;
(base*=base)%=MOD;
y>>=1ll;
}
return ret;
}
int main(){
cin>>n>>m>>k;
if((n&1)!=(m&1)) return puts("0"),0;
int x,y,w;
for(int i=1;i<=k;i++) cin>>x>>y>>w;
cin>>MOD;
cout<<qpow(2ll,max(0ll,1ll*(n-1)*(m-1)-k));
return 0;
}
说起来,全场可能就我不知道发了两个大样例(虽然后来证明没什么用..)
https://files.cnblogs.com/files/ghostcai/7.13.pdf
来源:https://www.cnblogs.com/ghostcai/p/9306712.html
