noip模拟15[夜莺与玫瑰·影子·玫瑰花精]
noip模拟15 solution
只有60分,还是苦想死想2h想出来的,就是第一题,然后后面就没有时间咧
所以整场只有\(60pts\),还是有暴力分没有拿到的
让我最后悔的事就是给\(T3\)留的时间太少了,导致线段树没有编译就交上去了\(compile\;error\)
不过说实话这个出题人是真的有才,小题目背景写的非常的不错,导致我看题目看了好久
·
T1 夜莺与玫瑰
这题面是真的有意境,但是这题是真难,真难,真难,真难
我还是把\(O(Tn^2)\)的算法给搞出来了,我的式子比较ex,比较难理解,我还是直接写题解吧
我们可以发现,所有对答案造成贡献的\(dead\;line\)的斜率一定不同,
就可以直接根据这个性质,只用(1,1)去跟其他的点连边,我们要求(i-1)与(j-1)互质
(这里为了简化计算,用(0,0)来和其他连边,直接判断(i,j)是否互质)
所以我们直接\(O(n^2)\)枚举\(O(logn)\)判断是否互质,得到答案
这样我们找到了所有的斜率,然鹅相同的斜率还会有平行的直线,所以我们对这些分别乘上系数,就是\((n-i)(n-j)\)
当然我们一定会算重一些线,那些头尾相接的线,这个系数是需要减去的
因为每两个线就会合成一条线,这个可以通过小矩形合并成大矩形来解释,系数是\((n-2*i)(m-2*i)\)
我们最终得到的式子就是:
\(ans=\sum\limits^{n-1}_{i=1}\sum\limits^{m-1}_{j=1}[gcd(i,j)==1]((n-i)(m-j)-max(0,n-i*2)max(0,m-j*2)))\)
中括号就是0,1;
所以题解上直接告诉我对这个玩意取一个前缀和,没给我气死,啥玩意不说明白点,气人!!!!
经过我的一番思考之后,我把这个前缀和搞了出来,复杂度\(O(n^2logn)\)
继续观察式子,只观察前一半,先不管后面的max,自己推一推就很容易发现,前面的加和就是gcd的前缀和的前缀和,两遍!!
因为每一个数都加了它该加的次数就是一个前缀和的前缀和,这个是\(n^2logn\)的,询问是\(O(1)\)的
这个怎么说??对后面的max进行化简
\(\sum\limits^{n-1}_{i=1}\sum\limits^{m-1}_{j=1}[gcd(i,j)==1](n-i*2)(m-j*2)\)
\(\sum\limits^{n-1}_{i=1}\sum\limits^{m-1}_{j=1}[gcd(i,j)==1]2(\dfrac{n}{2}-i)2(\dfrac{m}{2}-j)\)
\(\sum\limits^{n-1}_{i=1}\sum\limits^{m-1}_{j=1}[gcd(i,j)==1]4(\dfrac{n}{2}-i)(\dfrac{m}{2}-j)\)
你就突然发现这后面其实就是一个\(f[\dfrac{n}{2}][\dfrac{m}{2}]\)
但是呢,你后面还要处理一些边界问题,奇数的时候,中间会有剩余的一行,这样会漏条件,所以我们要把整个图完整的分成四块
这里具体看代码
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=4005;
const int mod=1<<30;
int T,n,m;
int c[N][N],f[N][N];
ll ans;
int GCD(int x,int y){
return y==0?x:GCD(y,x%y);
}
signed main(){
scanf("%d",&T);
n=4001;m=4001;
for(re i=2;i<=n;i++){
for(re j=2,now;j<=m;j++){
now=0;
if(GCD(i-1,j-1)==1){
now=1;
}
c[i][j]=(1ll*now+c[i-1][j]+c[i][j-1]+mod-c[i-1][j-1])%mod;
}
}
for(re i=2;i<=n;i++){
for(re j=2;j<=m;j++){
f[i][j]=(1ll*f[i-1][j]+f[i][j-1]+mod-f[i-1][j-1]+c[i][j])%mod;
}
}
//cout<<c[1][2]<<endl;
//cout<<"sb"<<endl;
for(re i=1;i<=T;i++){
scanf("%d%d",&n,&m);
int nl=n>>1,nr=n+1>>1;
int ml=m>>1,mr=m+1>>1;
//cout<<nl<<" "<<nr<<" "<<ml<<" "<<mr<<endl;
ans=(1ll*f[n][m]+1ll*mod*4-f[nl][ml]-f[nl][mr]-f[nr][ml]-f[nr][mr])%mod;
//cout<<ans<<endl;
ans=(ans*2)%mod;
ans=(ans+n+m)%mod;
printf("%lld\n",ans);
}
}
这个是\(O(n^2logn)\)的复杂度,然而在JYXHYX的\(O(n^2)\)求\(GCD\)的优化下成功达到\(O(n^2)\),但是好像还有人\(O(n)\)....
·
T2 影子
叫你寻找\(min*sum\)的最大值,所以我直接\(O(n^2logn)\)暴力循环加LCA走了
在考场上的时候,脑子里是有一瞬间想到点分治的,然后瞬间想到忘干净了
考场下来,用点分治实现了一下,每次找重心都以权值最小的点为中心,然后只拿到了55pts
点分治_55pts
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e5+5;
int T,n;
ll vmn[N],ans;
int to[N*2],nxt[N*2],head[N],rp;
ll val[N*2];
void add_edg(int x,int y,ll z){
to[++rp]=y;
val[rp]=z;
nxt[rp]=head[x];
head[x]=rp;
}
ll ms[N],su,mx,rt;
bool vis[N];
void get_rt(int x,int f){
ms[x]=0x3f3f3f3f;
for(re i=head[x];i;i=nxt[i]){
int y=to[i];
if(y==f||vis[y])continue;
get_rt(y,x);
}
if(vmn[x]<mx)mx=vmn[x],rt=x;
}
int cnt;
struct node{
ll len,bl;
}rot[N];
void get_dis(int x,int f,ll d,int bl){
rot[++cnt].len=d;
rot[cnt].bl=bl;
for(re i=head[x];i;i=nxt[i]){
int y=to[i];
if(y==f||vis[y])continue;
get_dis(y,x,d+val[i],bl);
}
}
void get_Dis(int x){
cnt=0;
for(re i=head[x];i;i=nxt[i]){
int y=to[i];
//cout<<y<<" ";
if(vis[y])continue;
get_dis(y,x,val[i],y);
}
}
ll maxn,maxx,id;
ll get_ans(int x){
get_Dis(x);
maxn=0;maxx=0;id=0;
for(re i=1;i<=cnt;i++){
//cout<<rot[i].len<<" "<<rot[i].bl<<endl;
if(rot[i].len>maxn&&rot[i].bl!=id){
maxx=maxn;maxn=rot[i].len;
id=rot[i].bl;
}
else if(rot[i].len>maxn)maxn=rot[i].len;
else if(rot[i].len>maxx&&rot[i].bl!=id)
maxx=rot[i].len;
}
//cout<<maxn<<" "<<maxx<<endl;
return maxn+maxx;
}
void sol(int x){
vis[x]=true;
ans=max(ans,1ll*get_ans(x)*vmn[x]);
for(re i=head[x];i;i=nxt[i]){
int y=to[i];
if(vis[y])continue;
mx=0x3f3f3f3f;
get_rt(y,x);
sol(rt);
}
}
void clear(){
rp=0;memset(head,0,sizeof(head));
memset(vis,false,sizeof(vis));
}
signed main(){
scanf("%d",&T);
while(T--){
clear();
scanf("%d",&n);
for(re i=1;i<=n;i++)scanf("%lld",&vmn[i]);
for(re i=1,x,y;i<n;i++){
ll z;scanf("%d%d%lld",&x,&y,&z);
add_edg(x,y,z);add_edg(y,x,z);
}
mx=0x3f3f3f3f;
get_rt(1,0);
//cout<<"rt="<<rt<<endl;
ans=0;sol(rt);
printf("%lld\n",ans);
}
}
我也不知道是我的思路错了还是点分治真的不能过。。。
·
所以我开开心心的去打并查集了,这个非常好想
先把所有的点按照权值从大到小排序,按照顺序插入到并查集里,然后每次在并查集中寻找最长链。
这里有一个结论,将两个联通块合并时,合并后的最长链的端点就是原来四个端点的其中两个
枚举六种情况,即使是原来的两条链最长,之前的权值比现在的大,所以不会对答案造成任何影响
这里可以直接使用树链剖分或者倍增直接求lca求距离
并查集_AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e5+5;
int T,n;
ll ans;
struct POT{
ll rmn,id;
bool operator < (POT x)const{
return x.rmn>rmn;
}
}sca[N];
priority_queue<POT> q;
int to[N*2],nxt[N*2],head[N],rp;
ll val[N*2];
void add_edg(int x,int y,ll z){
to[++rp]=y;
val[rp]=z;
nxt[rp]=head[x];
head[x]=rp;
}
int dfn[N],cnt,fa[N];
int siz[N],son[N],top[N];
ll dep[N];
void dfs1(int x,int f){
siz[x]=1;son[x]=0;
for(re i=head[x];i;i=nxt[i]){
int y=to[i];
if(y==f)continue;
fa[y]=x;dep[y]=dep[x]+val[i];
dfs1(y,x);
siz[x]+=siz[y];
if(!son[x]||siz[y]>siz[son[x]])son[x]=y;
}
}
void dfs2(int x,int f){
top[x]=f;dfn[x]=++cnt;
if(son[x])dfs2(son[x],f);
for(re i=head[x];i;i=nxt[i]){
int y=to[i];
if(y==son[x]||y==fa[x])continue;
dfs2(y,y);
}
}
int LCA(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int fai[N],d[N][2];
ll DIS(int x,int y){
return dep[x]+dep[y]-2*dep[LCA(x,y)];
}
ll ds[N];
int find(int x){
return x==fai[x]?x:fai[x]=find(fai[x]);
}
bool vis[N];
void clear(){
rp=0;memset(head,0,sizeof(head));
cnt=0;dep[1]=1;ans=0;
memset(vis,false,sizeof(vis));
for(re i=1;i<=n;i++){
fai[i]=i;
d[i][0]=d[i][1]=i;
ds[i]=0;
}
}
signed main(){
//freopen("b.ans","w",stdout);
scanf("%d",&T);
while(T--){
scanf("%d",&n);
clear();
for(re i=1;i<=n;i++){
scanf("%lld",&sca[i].rmn);sca[i].id=i;
q.push(sca[i]);
}
for(re i=1,x,y;i<n;i++){
ll z;scanf("%d%d%lld",&x,&y,&z);
add_edg(x,y,z);add_edg(y,x,z);
}
dfs1(1,0);dfs2(1,1);
while(!q.empty()){
int now=q.top().id;q.pop();
for(re i=head[now];i;i=nxt[i]){
int y=to[i];
if(sca[y].rmn<sca[now].rmn)continue;
int fn=find(now),fy=find(y);
int ld,rd;
ll len=0,getl;
if(len<ds[fn]){len=ds[fn];ld=d[fn][0];rd=d[fn][1];}
if(len<ds[fy]){len=ds[fy];ld=d[fy][0];rd=d[fy][1];}
if(len<(getl=DIS(d[fn][0],d[fy][0]))){len=getl;ld=d[fn][0];rd=d[fy][0];}
if(len<(getl=DIS(d[fn][0],d[fy][1]))){len=getl;ld=d[fn][0];rd=d[fy][1];}
if(len<(getl=DIS(d[fn][1],d[fy][0]))){len=getl;ld=d[fn][1];rd=d[fy][0];}
if(len<(getl=DIS(d[fn][1],d[fy][1]))){len=getl;ld=d[fn][1];rd=d[fy][1];}
fai[fn]=fy;ds[fy]=len;d[fy][0]=ld;d[fy][1]=rd;
int f=find(now);
ans=max(ans,ds[f]*sca[now].rmn);
}
}
printf("%lld\n",ans);
}
}
·
T3 玫瑰花精
所以这个题有没有让你想到hotel这道题???
可以说是完全一模一样的,
在这个题中,注意左边最优,注意取等问题
注意边界问题,左右边界不需要除以2
然后就维护三个变量组:
1.mmx:最大区间的长度 lmx:最大区间的左端点 rmx:最大区间的右端点
2.mlx:左端点为l的区间最大长度 rlx:左区间的右端点
3.mrx:右端点为r的区间最大长度 lrx:右区间的左端点
直接上代码
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=2e5+5;
int n,m;
int bl[1000005];
struct XDS{
#define ls x<<1
#define rs x<<1|1
int mmx[N*8],mlx[N*8],mrx[N*8];
int lmx[N*8],rmx[N*8],rlx[N*8],lrx[N*8];
int rex,rel,rer;
void pushup(int x,int l,int r){
int mid=l+r>>1;
mmx[x]=mmx[ls];lmx[x]=lmx[ls];rmx[x]=rmx[ls];
if(((mmx[x]+1)>>1)<((mlx[rs]+mrx[ls]+1)>>1))
{mmx[x]=mlx[rs]+mrx[ls];lmx[x]=lrx[ls];rmx[x]=rlx[rs];}
if(((mmx[x]+1)>>1)<((mmx[rs]+1)>>1))
{mmx[x]=mmx[rs];lmx[x]=lmx[rs];rmx[x]=rmx[rs];}
mlx[x]=mlx[ls];rlx[x]=rlx[ls];
if(mlx[x]==mid+1-l){mlx[x]+=mlx[rs];rlx[x]=rlx[rs];}
mrx[x]=mrx[rs];lrx[x]=lrx[rs];
if(mrx[x]==r-mid){mrx[x]+=mrx[ls];lrx[x]=lrx[ls];}
return ;
}
void build(int x,int l,int r){
mlx[x]=mrx[x]=mmx[x]=r-l+1;
lmx[x]=lrx[x]=l;
rlx[x]=rmx[x]=r;
if(l==r)return ;
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
return;
}
void ins(int x,int l,int r,int pos,int v){
if(l==r){
if(v==2){
mmx[x]=1;mlx[x]=1;mrx[x]=1;
lmx[x]=pos;rmx[x]=pos;lrx[x]=pos;rlx[x]=pos;
}
else{
mmx[x]=0;mlx[x]=0;mrx[x]=0;
lmx[x]=pos+1;rmx[x]=pos-1;lrx[x]=pos+1;rlx[x]=pos-1;
}
return ;
}
int mid=l+r>>1;
if(pos<=mid)ins(ls,l,mid,pos,v);
else ins(rs,mid+1,r,pos,v);
pushup(x,l,r);
return ;
}
#undef ls
#undef rs
}xds;
bool vis[N];
signed main(){
scanf("%d%d",&n,&m);
xds.build(1,1,n);
for(re i=1,typ,x;i<=m;i++){
scanf("%d%d",&typ,&x);
if(typ==2){
xds.ins(1,1,n,bl[x],2);
vis[bl[x]]=0;
}
else{
int p=(xds.rmx[1]+xds.lmx[1])>>1;
if(xds.lmx[1]==1||xds.mlx[1]>=(xds.mmx[1]+1)/2)p=1;
else if(xds.rmx[1]==n||xds.mrx[1]>(xds.mmx[1]+1)/2)p=n;
printf("%d\n",p);
xds.ins(1,1,n,p,1);
vis[p]=1;
bl[x]=p;
}
}
}
就这几个小边界问题,我对着数据调了一晚上。。。。。