20221104
20221104
susliks
思路
这道题第一眼看去并没有什么思路。但暴力的思路还是很好想的,枚举 \(r,c\) 然后每次暴力枚举敲击区域的左上角位置,很容易发现,如果我们敲击的位置有 \(i\) 只地鼠,那我们就必须在这个位置上敲击 \(i\) 次,所以我们只需要给敲击区域上的所有点减去 \(i\) 然后判断是否可行即可。 (然后考试的时候上了个厕所就忘了) 然后再考虑如何继续优化这个暴力,可以发现每次敲击都必定会打掉 \(r\times c\) 只地鼠,换言之,每次敲击都可以使地鼠总量减少 \(r\times c\) ,所以可以打掉所有地鼠,并且每次都必须打掉 \(r\times c\) 只地鼠的锤子大小 \(r\times c\) 必然是总地鼠数的约数。这样就可以将原本 \(O(n^6)\) 的暴力优化到 \(O(n^4k)\) ( \(k\) 为总地鼠数的约数个数的组合方案数),当然原本的 \(O(n^6)\) 中本来就有 \(n^2\) 其实是 \(r\times c\) 。然后差不多就能过了。(然而枚举 \(r,c\) 的时候出问题了,直接白给 100pts )
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
x=0;int f=0;char c=getchar();
for(;!isdigit(c);c=getchar())f|=(c=='-');
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
x=f?~x+1:x;
}
template<typename T>inline void out(T x){
if(x<0)x=~x+1,putchar('-');
if(x>9)out(x/10);
putchar(x%10^48);
}
const int N=105;
int m,n;
int mp[N][N],gg[N][N];
ll sum,ans;
inline bool check(int x,int y){
fo(i,1,m)
fo(j,1,n)gg[i][j]=mp[i][j];
fo(i,1,m-x+1){
fo(j,1,n-y+1){
if(gg[i][j]){
int z=gg[i][j];
for(int k=0;k<x;k++){
for(int l=0;l<y;l++){
gg[i+k][j+l]-=z;
if(gg[i+k][j+l]<0)return 0;
}
}
}
}
}
fo(i,1,m)
fo(j,1,n)
if(gg[i][j])return 0;
return 1;
}
int main(){
in(m),in(n);
fo(i,1,m){
fo(j,1,n){
in(mp[i][j]);
sum+=mp[i][j];
}
}
ans=sum;
for(int i=m;i>=1;--i){
for(int j=n;j>=1;--j){
if(sum%(i*j)==0&&sum/i/j<ans){
if(check(i,j)){
ans=sum/i/j;
}
}
}
}
out(ans);
return 0;
}
fire
前言
考试的时候什么都想出来了,就是最后统计答案的时候挂了,又白送出题人 \(100pts\) 。当然也可能是因为时间分配不均的原因,打到最后统计答案的时候就只剩下 \(10\) 分钟了,在第一题浪费的时间过于多了,以后一定要严格控制每道题时间。
思考
如果做过与树的直径有关的题,应该很容易想到与树的直径有关。这里我们可以小小证明一下。
树的直径
假设 \(1,2,3,4\) 即路径 \(1\) -> \(4\) 是树的直径上的点,如果我们选择的路径不在树的直径上,假设我们选择不在树的直径上的路径 \(4,3,2,6,8\) 即路径 \(4\) -> \(8\) ,因为 \(1\) 是树的直径上的点,所以 \(2\) 到 \(1\) 的距离一定大于 \(2\) 到 \(8\) 的距离,所以选择路径 \(4\) -> \(8\) 剩余的最大距离就是 \(2\) 到 \(1\) 的距离,而选择树的直径剩余的最大距离就是 \(2\) 到 \(8\) 的距离。但是 \(2\) 到 \(1\) 的距离又大于 \(2\) 到 \(8\) 的距离,所以选树的直径上的点一定更优。
统计答案
找到树的直径后统计答案就很简单了。很容易想到枚举直径上的两个距离不超过 \(s\) 的端点,然后得到左端点左边部分与右端点右边部分,还有左右端点之内最长的子树三者之中的最大值即可。对于直径上的最长长度可以用前缀和预处理出来。而对于直径上的点的子树的最长长度也可以用 \(dp\) 预处理出来。最后,因为是前缀和,其实对于每个点我们都可以直接二分找到相距不超过 \(s\) 的最远的点。这样,统计答案的时间复杂度就优化到 \(O(nlogn)\) 的了。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
x=0;int f=0;char c=getchar();
for(;!isdigit(c);c=getchar())f|=(c=='-');
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
x=f?~x+1:x;
}
template<typename T>inline void out(T x){
if(x<0)x=~x+1,putchar('-');
if(x>9)out(x/10);
putchar(x%10^48);
}
inline int maX(int a,int b){
return a>b?a:b;
}
const int N=3e5+5;
int n,s;
struct edge{
int v,nex,w;
}e[N<<1];
int head[N],tot;
inline void add(int u,int v,int w){e[++tot].v=v,e[tot].w=w,e[tot].nex=head[u],head[u]=tot;}
ll dis[N];
int st,ed,maxnn;
inline void dfs(int x,int fa){//求树的直径
if(dis[x]>maxnn){
ed=x;
maxnn=dis[x];
}
for(int i=head[x];i;i=e[i].nex){
int v=e[i].v;
if(v==fa)continue;
dis[v]=dis[x]+e[i].w;
dfs(v,x);
}
}
int pre[N],nex[N];//类似链表,用于剖出直径
bool f;
int gg[N];//按照原编号记录的前缀和
inline void dfs_cl(int x,int fa){//将直径剖出
if(f)return;
if(x==ed){
f=1;
return;
}
for(int i=head[x];i;i=e[i].nex){
int v=e[i].v;
if(v==fa)continue;
if(f)return;
pre[v]=x;
nex[x]=v;
gg[v]=gg[x]+e[i].w;
dfs_cl(v,x);
}
}
int dfn[N],bf[N],lon[N],cnt;
//dfn为按顺序编号后的直径上的点的编号,bf为编号的好之前的编号,lon为按原编号记录的子树最长深度编号
inline void DFS(int x,int fa,int root){//求直径上每个点的最长子树长度
for(int i=head[x];i;i=e[i].nex){
int v=e[i].v;
if(v==fa||v==nex[root]||v==pre[root])continue;
DFS(v,x,root);
lon[x]=maX(lon[x],lon[v]+e[i].w);
}
}
int len[N];//编好号之后的最长子树深度
int F[N][21];
inline void ST_prwork(){
fo(i,1,cnt)F[i][0]=len[i];
int t=log(n)/log(2)+1;
fo(j,1,t-1)
fo(i,1,n-(1<<j)+1)
F[i][j]=max(F[i][j-1],F[i+(1<<(j-1))][j-1]);
return;
}
inline int ST_query(int l,int r){
int k=log(r-l+1)/log(2);
return max(F[l][k],F[r-(1<<k)+1][k]);
}
int gerp[N];//编号号后的前缀和
int main(){
in(n),in(s);
int a,b,c;
pre[1]=nex[1]=1;
fo(i,2,n){
in(a),in(b),in(c);
add(a,b,c);
add(b,a,c);
pre[i]=nex[i]=i;
}
dfs(1,1);
st=ed;
maxnn=0;
memset(dis,0,sizeof(dis));
dfs(st,st);//求树的直径
dfs_cl(st,st);//剖出直径
int now=st;
while(nex[now]!=now){//重新编号
dfn[now]=++cnt;
bf[cnt]=now;
now=nex[now];
}
dfn[now]=++cnt;
bf[cnt]=now;
fo(i,1,cnt)DFS(bf[i],bf[i],bf[i]);//最长子树
fo(i,1,cnt)gerp[i]=gg[bf[i]],len[i]=lon[bf[i]];
int ans=0x3f3f3f3f;
ST_prwork();
fo(i,1,cnt){
int pp=upper_bound(gerp+i,gerp+cnt+1,s+gerp[i])-gerp-1;
ans=min(ans,max(max(gerp[i],gerp[cnt]-gerp[pp]),ST_query(i,pp)));
}
out(ans);
return 0;
}
dye
思路
一眼树链剖分。只需要在线段树中多记录一个左端点和右端点的颜色,再在合并的时候判断是否相同,相同就减一即可。
咳,貌似写得太简单了,补充一下。。。
线段树合并
对于这道题,线段树合并的操作稍有不同。
对于这样的两个区间,左区间的颜色段个数是 \(3\) ,右区间的颜色段数是 \(2\) ,直接相加得到 \(5\) ,但实际上是 \(4\) ,其实就是因为它们合并时中间段的颜色相同,导致黑色段在左边被算了一次,还在右边被算了一次,总共就被多算了1次。所以当两区间合并时相接部分颜色相同时答案减一即可。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
x=0;int f=0;char c=getchar();
for(;!isdigit(c);c=getchar())f|=(c=='-');
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
x=f?~x+1:x;
}
template<typename T>inline void out(T x){
if(x<0)x=~x+1,putchar('-');
if(x>9)out(x/10);
putchar(x%10^48);
}
const int N=100005<<1;
int n,m;
int w[N];
struct edge{
int v,nex;
}e[N<<1];
int head[N],tot;
inline void add(int u,int v){e[++tot].v=v,e[tot].nex=head[u],head[u]=tot;}
int fa[N],dep[N],size[N],wson[N];
inline void dfs(int x){
size[x]=1;
for(int i=head[x];i;i=e[i].nex){
int v=e[i].v;
if(v==fa[x])continue;
fa[v]=x;
dep[v]=dep[x]+1;
dfs(v);
size[x]+=size[v];
if(size[v]>size[wson[x]])wson[x]=v;
}
}
int top[N],dfn[N],cnt,pre[N];
inline void dfs_tp(int x,int topf){
dfn[x]=++cnt,pre[cnt]=x,top[x]=topf;
if(wson[x])dfs_tp(wson[x],topf);
for(int i=head[x];i;i=e[i].nex){
int v=e[i].v;
if(v==wson[x]||v==fa[x])continue;
dfs_tp(v,v);
}
}
struct node{
int l,r,lch,rch,val,tag;
int lc,rc;
node(){
tag=0;
}
}rt[N<<1];
int tote;
#define ls rt[x].lch
#define rs rt[x].rch
inline void push_up(int x){
rt[x].val=rt[ls].val+rt[rs].val;
if(rt[ls].rc==rt[rs].lc)--rt[x].val;
rt[x].lc=rt[ls].lc;
rt[x].rc=rt[rs].rc;
return ;
}
inline int build(int l,int r){
++tote;
int x=tote;
rt[x].l=l,rt[x].r=r;
if(l==r){
rt[x].val=1;
rt[x].lc=w[pre[l]];
rt[x].rc=w[pre[l]];
return x;
}
int mid=l+r>>1;
rt[x].lch=build(l,mid);
rt[x].rch=build(mid+1,r);
push_up(x);
return x;
}
inline void push_down(int x){
if(rt[x].tag!=1)return;
rt[ls].val=rt[rs].val=1;
rt[ls].lc=rt[ls].rc=rt[rs].lc=rt[rs].rc=rt[x].lc;
rt[x].tag=0;
rt[ls].tag=rt[rs].tag=1;
return;
}
inline void modify(int x,int l,int r,int v){
if(l<=rt[x].l&&rt[x].r<=r){
rt[x].val=1;
rt[x].lc=v;
rt[x].rc=v;
rt[x].tag=1;
return;
}
int mid=rt[x].l+rt[x].r>>1;
push_down(x);
if(l<=mid)modify(ls,l,r,v);
if(r>mid)modify(rs,l,r,v);
push_up(x);
return;
}
inline int query(int x,int l,int r){
if(l<=rt[x].l&&rt[x].r<=r)return rt[x].val;
int mid=rt[x].l+rt[x].r>>1;
int tmp=0;
push_down(x);
if(l<=mid)tmp+=query(ls,l,r);
if(r>mid){
if(tmp>0&&rt[ls].rc==rt[rs].lc)--tmp;
tmp+=query(rs,l,r);
}
push_up(x);
return tmp;
}
inline int queryc(int x,int pos){
if(rt[x].l==rt[x].r)return rt[x].lc;
int mid=rt[x].l+rt[x].r>>1;
int tmp;
push_down(x);
if(pos<=mid)tmp=queryc(ls,pos);
else tmp=queryc(rs,pos);
push_up(x);
return tmp;
}
#undef ls
#undef rs
inline void lcam(int x,int y,int v){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
modify(1,dfn[top[x]],dfn[x],v);
x=fa[top[x]];
}
if(dep[x]<dep[y])swap(x,y);
modify(1,dfn[y],dfn[x],v);
return;
}
inline int lcaq(int x,int y){
int ans=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
ans+=query(1,dfn[top[x]],dfn[x]);
if(queryc(1,dfn[top[x]])==queryc(1,dfn[fa[top[x]]]))--ans;
x=fa[top[x]];
}
if(dep[x]<dep[y])swap(x,y);
ans+=query(1,dfn[y],dfn[x]);
return ans;
}
int main(){
in(n),in(m);
fo(i,1,n)in(w[i]);
int u,v;
fo(i,2,n){
in(u),in(v);
add(u,v);
add(v,u);
}
fa[1]=1;
dfs(1);
dfs_tp(1,1);
build(1,n);
char Op;
int a,b,c;
fo(i,1,m){
cin>>Op;
if(Op=='C'){
in(a),in(b),in(c);
lcam(a,b,c);
}
else{
in(a),in(b);
out(lcaq(a,b)),putchar('\n');
}
}
return 0;
}