noip模擬賽34 Merchant Equation Rectangle
Merchant
考場思路:
考場上想到了二分,但是並不知道nth_element這種高階的東西,於是用了優先佇列。但是由於忘記了清空佇列,所以掛了。
正解:
由題意可知,每一個物品的集合必定是一個一次函式。隨著時間的增加,最大值是先降後增的。在下降區段,必定是沒有零點優的,所以只需要先特判零點,然後對於以後的時間進行二分,使用nth_element,O(N)求出最小的n-m個值,選出剩下的至多m個值,判斷是否符合條件,變更l,r。
tip:nth_element(a+1,a+m+1,a+n+1);能夠在O(n)時間內處理出最小的m個值(是無序的)。
#include<bits/stdc++.h> #define lt long long #define int long long using namespace std; const int N=10000050; int n,m; lt s; lt k[N],b[N],a[N]; lt ans,ens; signed main(){ scanf("%lld%lld%lld",&n,&m,&s); for(int i=1;i<=n;++i){ scanf("%lld%lld",&k[i],&b[i]); a[i]=b[i]; } sort(a+1,a+1+n); for(int i=n;i>=n-m+1;i--){ ans+=a[i]; if(ans>=s){ printf("0\n"); return 0; } } lt l=0,r=10000000005; while(1<r-l){ lt mid=(r+l)>>1; for(int i=1;i<=n;++i){ a[i]=k[i]*mid+b[i]; } nth_element(a+1,a+n-m+1,a+n+1); bool b=0; ans=0; for(int i=n;i>=n-m+1;--i){ if(a[i]>0)ans+=a[i]; if(ans>=s){ b=1; break; } } if(b){ r=mid; } else{ l=mid; } // printf("%lld %lld %lld\n",mid,ans,s); } cout<<r<<endl; return 0; }
Equation
考場思路:
考場上想到了正解,然鵝由於失智行為,只得了3分(不刪freopen都能得這麼多分)
正解:
從根節點向下按照式子推,可以發現,每一個數都可以表示成 \(x_{i} =k+x_{1}\) 或 \(x_{i} =k-x_{1}\) 的形式,此時對於詢問我們便可以輕鬆的回答詢問了。對於修改操作我們發現,深度奇偶性相同的兩個點,修改其中一個點,對於另一個的貢獻是相同的;而對於相反的點,修改其中一個點,對於另一個的貢獻是相反的。知道了這一點我們就可以建出線段樹(用樹狀陣列更快,線段樹要大力卡常)去維護修改值,詢問時對於奇數點與偶數點分別處理就好了。
## Rectangle ####考場思路: 考場沒思路……#include<bits/stdc++.h> #define lt long long using namespace std; const int N=1000050; int n,q; int head[N],tot; int dep[N],fa[N],id[N],rk[N],idnt,size[N]; lt t[N*4],val[N],yb[N]; struct edge{ int nxt,to,dis; }e[N*2]; inline int rd(){ int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } void ade(int x,int y,int z){ e[++tot].nxt=head[x]; e[tot].to=y; e[tot].dis=z; head[x]=tot; } void dfs1(int u){ id[u]=++idnt; rk[idnt]=u; size[u]=1; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; dep[v]=dep[u]+1; val[v]=e[i].dis-val[u]; dfs1(v); size[u]+=size[v]; } } inline void pushdown(int u){ if(!t[u]) return; t[u<<1]+=t[u]; t[u<<1|1]+=t[u]; t[u]=0; } void edit(int u,int l,int r,int ll,int rr,lt x){ if(l>rr||r<ll){ return; } if(l>=ll&&r<=rr){ t[u]+=x; return; } pushdown(u); register int mid=(l+r)>>1; edit(u<<1,l,mid,ll,rr,x); edit(u<<1|1,mid+1,r,ll,rr,x); } int ask(int u,int l,int r,int x){ if(l==r){ return t[u]; } pushdown(u); register int mid=(l+r)>>1,ans; if(x<=mid){ ans=ask(u<<1,l,mid,x); } else{ ans=ask(u<<1|1,mid+1,r,x); } return ans; } void que(int x,int y,lt z){ if((dep[x]&1)==(dep[y]&1)){ if(!(dep[x]&1)){ lt anx,any,cnx,cny,cx; anx=ask(1,1,n,id[x]); any=ask(1,1,n,id[y]); cnx=anx+val[x]; cny=any+val[y]; cx=cnx+cny-z; if(cx&1){ printf("none\n"); return; } else{ cx/=2; printf("%lld\n",cx); } } else{ lt cnx,cny,cx; cnx=ask(1,1,n,id[x]); cny=ask(1,1,n,id[y]); cnx=val[x]-cnx; cny=val[y]-cny; cx=z-cnx-cny; if(cx&1){ printf("none\n"); return; } else{ cx/=2; printf("%lld\n",cx); } } } else{ if(dep[x]&1){ lt cnx,cny; cnx=ask(1,1,n,id[x]); cny=ask(1,1,n,id[y]); cnx=val[x]-cnx; cny+=val[y]; if(cnx+cny==z){ printf("inf\n"); } else{ printf("none\n"); } } else{ swap(x,y); lt cnx,cny; cnx=ask(1,1,n,id[x]); cny=ask(1,1,n,id[y]); cnx=val[x]-cnx; cny+=val[y]; // printf("%d %d %d %d %d %d",x,y,anx,cnx,any,cny); if(cnx+cny==z){ printf("inf\n"); } else{ printf("none\n"); } } } } int main(){ // freopen("shuju.in","r",stdin); // freopen("my.out","w",stdout); scanf("%d%d",&n,&q); int fc,wc; for(register int i=2;i<=n;i++){ fc=rd(),wc=rd(); fa[i]=fc; yb[i]=wc; ade(fc,i,wc); } dep[1]=1; dfs1(1); int tp,uc,vc; lt sc,we; for(register int i=1;i<=q;++i){ tp=rd(); if(tp==1){ uc=rd(),vc=rd(),sc=rd(); que(uc,vc,sc); } else{ uc=rd(),we=rd(); if(!(dep[uc]&1)){ edit(1,1,n,id[uc],id[uc]+size[uc]-1,we-yb[uc]); yb[uc]=we; } else{ edit(1,1,n,id[uc],id[uc]+size[uc]-1,yb[uc]-we); yb[uc]=we; } } } return 0; }
正解:
很是難寫的一道題。
首先列舉矩形的右邊界,對於每一個有邊界,從右往左列舉左邊界
考慮左右邊界在A,B之間的矩形,都可以以A到B為長,對於在藍色區間的C點,和在橙色區間的D點,若兩者和A,B構成點集,則上邊界為C點,下邊界為D點,左邊界為B,右邊界為A。每這樣的兩對點都能對答案產生貢獻,於是得出以下式子
\(\displaystyle\sum_{i=1}^{mn}\displaystyle\sum_{j=mx}^{inf}(j-i)\)
將i從裡面提出來,定義size[A]為從零到A的節點數量,定義sum[A]為從零到A的節點的縱座標總和。
每一個C節點都有size[A]個貢獻,減去的D節點的和為sum[A]
\(\displaystyle\sum_{i=mx}^{inf}i*size[A]-sum[A]\)
再將i表示出來
\(sum[i]*size[j]-sum[j]*size[i]\)
如果再l與r上有多個點時
對於綠色部分顯然不能用B和A時維護,否則會有重複,所以在B和A時,只能維護藍色區間,要在C和A時,再去維護黃色區間。
對於sum和size使用樹狀陣列維護與查詢(每次移動右邊界時,清空樹狀陣列)
#include<bits/stdc++.h>
#define lt long long
using namespace std;
const int N=10050,mod=1e9+7;
int n;
int x[N],y[N];
vector<int> mc[3000];
int xmx,ymx;
int size[3000],sum[3000];
bool vis[3000][3000];
lt ens,anx,any;
int low(int x){
return x&-x;
}
void add(int x,int y){
x++;
for(int i=x;i<=ymx+1;i+=low(i)){
size[i]+=1;
sum[i]+=y;
}
}
void ask(int x,int y){
y++;
lt ana=0,anb=0,bna=0,bnb=0;
for(int i=x;i;i-=low(i)){
ana+=size[i];
anb+=sum[i];
}
for(int i=y;i;i-=low(i)){
bna+=size[i];
bnb+=sum[i];
}
// printf("[%d %d %d %d]\n",ana,anb,bna,bnb);
anx=bna-ana;any=bnb-anb;
// if(anx<0) anx=0;
// if(any<0) any=0;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d%d",&x[i],&y[i]);
xmx=max(xmx,x[i]);
ymx=max(ymx,y[i]);
mc[x[i]].push_back(y[i]);
}
for(int i=1;i<=xmx;++i){
sort(mc[i].begin(),mc[i].end());
mc[i].push_back(ymx+1);
}
for(int i=1;i<=xmx;++i){
// cout<<endl<<i<<" rk "<<ens<<endl;
memset(size,0,sizeof(size));
memset(sum,0,sizeof(sum));
int szi=mc[i].size()-1;
if(!szi)continue;
for(int k=0;k<szi;++k){
if(!vis[i][mc[i][k]]){
vis[i][mc[i][k]]=1;
add(mc[i][k],mc[i][k]);
}
}
for(int j=i-1;j>=1;--j){
int szj=mc[j].size()-1;
if(!szj) continue;
for(int k=0;k<szj;++k){
if(!vis[i][mc[j][k]]){
vis[i][mc[j][k]]=1;
add(mc[j][k],mc[j][k]);
}
}
int tp1=0,tp2=0,up=max(mc[i][0],mc[j][0]);
while(mc[i][tp1+1]<=up) tp1++;
while(mc[j][tp2+1]<=up) tp2++;
while(tp1<szi&&tp2<szj){
int uup=min(mc[i][tp1+1],mc[j][tp2+1]);
int down=min(mc[i][tp1],mc[j][tp2]);
int aa,bb,cc,dd;
ask(up,uup-1);
aa=anx,bb=any;
ask(1,down);
cc=anx,dd=any;
ens=(ens+((lt)(i-j)*(bb*cc-dd*aa)))%mod;
up=uup;
if(mc[i][tp1+1]<=up)tp1++;
if(mc[j][tp2+1]<=up)tp2++;
// printf("%d %d|",tp1,tp2);
}
}
}
printf("%lld\n",ens);
return 0;
}