noip模拟34 Merchant Equation Rectangle
好像又咕了好久的样子 \((\)\(\color{white}{{7)\lbrace}}\)
再来写一篇(
这次考得感觉是过去几天最好的一场(
考场上顺序开题。
\(\mathrm{A.}\mathbb{Merchant}\):不知道
\(\mathrm{B.}\mathbb{Equation}\):数据结构
\(\mathrm{C.}\mathbb{Rectangle}\):不知道
开始时,先把三道题都看了一遍,觉得没一道可做的QAQ
\(\mathrm{A}\) 和 \(\mathrm{C}\) 暂时没有什么好的思路,看到 \(\mathrm{B}\) 时:
内心OS:这不是以 \(\mathrm{dfs}\) 序为下标,建一棵线段树维护一堆东西的大水题嘛!
其实不是这样的。
某位julao曾经说过:如果你打算在考场上写 \(\mathrm{DS}\) 的话,就要承受抱灵的风险。
于是我就用了两个多小时连写带调带对拍,写完了 \(\mathrm{B}\)。
用最后的时间,写了道 \(\mathrm{A}\) 的暴力。
结果出分后,因为 \(\mathrm{A}\) 的傻逼错误,挂了 \(8\) 分,\(\mathrm{B}\) 题人均 \(86\)。
估分:\(8+100+0=108\)
实际:\(0+86+0=86\)
还是说正解吧(
A.Merchant
这题考场压根没看,最后只打了个 \(8\) 分的特判,结果还挂了。
正解 \(\color{white}{\mathrm{-7}}\)
设 \(f_i(t)=k_i\times t+b_i\),容易发现他们的最大值,即 \(\displaystyle f_{max}(t)=\max_{1\le i\le n}f_i(t)\) 是先减后增的。证明很容易,当某一个时刻 \(t^\prime\) 满足 \(f_{max}(t^\prime)>f_{max}(t^\prime-1)\)时,\(f_{max}\) 的斜率必为正,因此 \(f_{max}(t^\prime+1)>f_{max}(t^\prime)\),可以得出当 \(f_{max}\) 开始递增时,就不会再递减了。
知道了这个性质,我们就可以用二分来检验时刻 \(t^\prime\) 是否能作为答案,具体过程如下:
先判断 \(0\) 能否为最终答案,再进行二分,每次算出所有的 \(f_i\) 求出前 \(m\) 大的,若 \(f_i\) 为正,则加上,否则不加,判断是否大于 \(S\),求前 \(m\) 大可以用 \(\mathrm{nth\_element}\) 来实现。
总复杂度为 \(O(n\log 10^9)\)
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m;
long long slim;
long long k[N],b[N];
long long s[N],sum;
bool check(int t)
{
sum=0;
for(int i=1;i<=n;i++) s[i]=b[i]+k[i]*t;
nth_element(s+1,s+n-m,s+n+1);
for(int i=n-m+1;i<=n;i++)
{
if(s[i]<=0) continue;
sum+=s[i];
if(sum>=slim) return true;
}
return sum>=slim;
}
int main()
{
scanf("%d%d%lld",&n,&m,&slim);
for(int i=1;i<=n;i++) scanf("%lld%lld",&k[i],&b[i]);
if(check(0))
{
printf("0\n");
return 0;
}
int l=1,r=1e9+5;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%d\n",l);
return 0;
}
B.Equation
考场上发现这道题会做异常兴奋(
就是。。。
点名卡常线段树(QAQ
正解 \(\color{white}{\mathrm{-6}}\)
容易发现每一个点都能够表示成 \(s+x_1\) 或 \(s-x_1\) 的形式,并且正负与每个点深度的奇偶性相关。
对于查询操作,对 \(u\) 和 \(v\) 进行单点查询,若深度的奇偶性不同,则只有无解或无数组解两种可能,若奇偶性相同则需判断解出的 \(x_1\) 是否是 \(2\) 的倍数。
对于修改操作,若深度为奇数,子树区间 \(+w\),否则 \(-w\)。
时间复杂度为 \(O(q\log n)\),然而这道题卡线段树,只能拿树状数组写。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,q;
int f[N],w[N];
int cnt,head[N];
int p,id[N],dep[N],size[N];
long long tree[N];
struct Node
{
int to,nxt;
}e[N<<1];
void adde(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void add(int x,int y)
{
for(;x<=n;x+=x&-x) tree[x]+=y;
}
int ask(int x)
{
int ans=0;
for(;x;x-=x&-x) ans+=tree[x];
return ans;
}
void dfs(int u)
{
id[u]=++p;
size[u]=1;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==f[u]) continue;
dep[v]=dep[u]+1;
dfs(v);
size[u]+=size[v];
}
}
inline int read()
{
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*10+ch-48;
ch=getchar();
}
return x*f;
}
int main()
{
n=read();
q=read();
for(int i=2;i<=n;i++)
{
f[i]=read();
w[i]=read();
adde(i,f[i]);
adde(f[i],i);
}
dep[1]=1;
dfs(1);
for(int u=2;u<=n;u++)
{
if(dep[u]&1)
{
add(id[u],w[u]);
add(id[u]+size[u],-w[u]);
}
else
{
add(id[u],-w[u]);
add(id[u]+size[u],w[u]);
}
}
while(q--)
{
int opt=read();
if(opt==1)
{
int u=read(),v=read();
long long s=read();
long long xu=ask(id[u]),xv=ask(id[v]);
if(!(dep[u]&1)) xu=-xu;
if(!(dep[v]&1)) xv=-xv;
if((dep[u]+dep[v])&1) xu+xv==s?printf("inf\n"):printf("none\n");
else if(!(abs(xu+xv-s)&1))
{
if(dep[u]&1) printf("%d\n",(s-xu-xv)>>1);
else printf("%d\n",(xu+xv-s)>>1);
}
else printf("none\n");
}
else
{
int u=read();
long long dis=read();
long long t=dis;
dis-=w[u];
w[u]=t;
if(dep[u]&1)
{
add(id[u],dis);
add(id[u]+size[u],-dis);
}
else
{
add(id[u],-dis);
add(id[u]+size[u],dis);
}
}
}
return 0;
}
C.Rectangle
正解 \(\color{white}{\mathrm{-6}}\)
五篇好题解 \(\color{white}{\mathrm{-9}}\)
由柿子可以得出,对于一对 \(l,r\),只需记录它点的个数与点的纵坐标和,因此可以用树状数组维护,对于有多个点的情况,可以将其纵向分割成几个部分,分别计算贡献即可。
时间复杂度:\(O(2500n\log 2500)\)
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e4+5,XY=2500,mod=1e9+7;
int n;
int a[N][N];
long long tree[XY+5][2];
bool vis[XY+5][XY+5];
long long ans;
void add(int x,int opt,int y)
{
for(;x<=XY;x+=x&-x) tree[x][opt]+=y;
}
int ask(int x,int opt)
{
int ans=0;
for(;x;x-=x&-x) ans+=tree[x][opt];
return ans;
}
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%lld%lld",&x,&y);
a[x][++a[x][0]]=y;
}
for(int i=1;i<=XY;i++)
{
sort(a[i]+1,a[i]+a[i][0]+1);
a[i][a[i][0]+1]=XY+1;
}
for(int i=1;i<=XY;i++)
{
memset(tree,0,sizeof(tree));
if(!a[i][0]) continue;
for(int j=1;j<=a[i][0];j++)
{
if(vis[i][a[i][j]]) continue;
vis[i][a[i][j]]=1;
add(a[i][j],0,1);
add(a[i][j],1,a[i][j]);
}
for(int j=i-1;j>=1;j--)
{
if(!a[j][0]) continue;
for(int k=1;k<=a[j][0];k++)
{
if(vis[i][a[j][k]]) continue;
vis[i][a[j][k]]=1;
add(a[j][k],0,1);
add(a[j][k],1,a[j][k]);
}
int pi=1,pj=1,now=max(a[i][1],a[j][1]);
while(a[i][pi+1]<=now) pi++;
while(a[j][pj+1]<=now) pj++;
while(pi<=a[i][0]&&pj<=a[j][0])
{
int up=min(a[i][pi+1],a[j][pj+1]),down=min(a[i][pi],a[j][pj]);
ans=(ans+(i-j)*((ask(up-1,1)-ask(now-1,1))*ask(down,0)-(ask(up-1,0)-ask(now-1,0))*ask(down,1)))%mod;
now=up;
if(a[i][pi+1]<=now) pi++;
if(a[j][pj+1]<=now) pj++;
}
}
}
printf("%lld\n",ans);
return 0;
}
\(\color{white}\rbrace\)

浙公网安备 33010602011771号