2018.10.9模拟考试
T1 一道很水的贪心。(然而蒟蒻博主一如既往地没A)
维护一个小根堆,考虑在每次插入元素时:
1.若新元素比堆顶小,说明在此时买入必然比在堆顶时买入更优,因此把新元素直接插入堆中。
2.若新元素比堆顶大,说明在此时卖出必然能获得收益,因此将收益累计入答案中,再把堆顶删除。
同时还要把新元素两次插入堆中。
原因:在最优解中,此元素可能并非作为“卖出”的一部分,而是作为“未进行操作”的一部分。插
入一次可以使此元素从“卖出”状态反悔到“未进行操作”状态。同样,再次插入可以使此元
素从“未进行操作”状态反悔到“买入”状态,从而保证了所有情况均被考虑。
#include<algorithm>//STL通用算法 #include<bitset>//STL位集容器 #include<cctype> #include<cmath> #include<complex> #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> #include<deque>//STL双端队列容器 #include<list>//STL线性列表容器 #include<map>//STL映射容器 #include<iostream> #include<queue>//STL队列容器 #include<set>//STL集合容器 #include<stack>//STL堆栈容器 #include<utility>//STL通用模板类 #include<vector>//STL动态数组容器 #define INF 0x3f3f3f3f #define ll long long using namespace std; ll n,ans; priority_queue<ll,vector<ll>,greater<ll> > que; signed main() { freopen("trade.in","r",stdin); freopen("trade.out","w",stdout); scanf("%lld",&n); for(ll i=1;i<=n;i++) { ll u;scanf("%lld",&u); if(que.empty()) que.push(u); else if(u<que.top()) que.push(u); else ans+=u-que.top(),que.pop(),que.push(u),que.push(u); }printf("%lld\n",ans); return 0; }
T2 一道第一眼看不出正确算法的题。
看一眼数据范围,n<=10^5。显然O(nlogn)(吗?)
推一推式子,发现并没有O(nlogn)的式子出现。
emmm...算了,还是打部分分吧。
看一眼部分分,嗯,前4个点暴力O(n^2)就能水过去。
等下...这中间6个点是怎么回事?n全部相等?m全部相等?
瞬间茅塞顿开——莫队。
这告诉我们要多关心部分分中的条件,也许正解就藏在其中。
没什么好说的,计算式也很好推,唯一要注意的就是要把所有除法替换成乘逆元。
#include<algorithm>//STL通用算法 #include<bitset>//STL位集容器 #include<cctype> #include<cmath> #include<complex> #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> #include<deque>//STL双端队列容器 #include<list>//STL线性列表容器 #include<map>//STL映射容器 #include<iostream> #include<queue>//STL队列容器 #include<set>//STL集合容器 #include<stack>//STL堆栈容器 #include<utility>//STL通用模板类 #include<vector>//STL动态数组容器 #define INF 0x3f3f3f3f #define ll long long #define MOD 1000000007 using namespace std; ll T,Q,N,M,siz,fac[100001],inv[100001],blk[100001],ans[100001]; struct uio{ ll n,m,id; }qry[100001]; ll qpow(ll x,ll y) { ll tmp=1;while(y) {if(y&1) (tmp*=x)%=MOD; (x*=x)%=MOD,y/=2;} return tmp; } ll C(int x,int y) { if(x<y) return 0; return fac[x]*inv[y]%MOD*inv[x-y]%MOD; } bool cmp(uio x,uio y) {return (blk[x.n]==blk[y.n]? x.m<y.m:blk[x.n]<blk[y.n]);} void Init() { fac[0]=1; for(ll i=1;i<=N;i++) fac[i]=fac[i-1]*i%MOD; inv[N]=qpow(fac[N],MOD-2); for(ll i=N-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%MOD; siz=sqrt(N); for(ll i=1;i<=N;i++) blk[i]=(i-1)/siz+1; sort(qry+1,qry+1+Q,cmp); } signed main() { freopen("sum.in","r",stdin); freopen("sum.out","w",stdout); scanf("%lld%lld",&T,&Q); for(ll i=1;i<=Q;i++) scanf("%lld%lld",&qry[i].n,&qry[i].m), qry[i].id=i,N=max(N,qry[i].n),M=max(M,qry[i].m); Init(); ll n=1,m=-1,tmp=0; for(ll i=1;i<=Q;i++) { while(n<qry[i].n) ((tmp*=2)-=C(n,m)-MOD)%=MOD,n++; while(m<qry[i].m) m++,(tmp+=C(n,m))%=MOD; while(m>qry[i].m) (tmp-=C(n,m)-MOD)%=MOD,m--; while(n>qry[i].n) n--,((tmp+=C(n,m))*=inv[2])%=MOD; //嗯对博客最后一句说的就是它------------^^^^^^^^------------ ans[qry[i].id]=tmp; } for(ll i=1;i<=Q;i++) printf("%lld\n",ans[i]); return 0; }
T3 一道丧心病狂的题目...
对于第一种查询,只需求出每行楼盘个数的前缀和即可。可以开2个sum数组分别表示"行前缀和"和"列差分"。
对于第二种查询,用并查集维护。可以开n个行vector和m个列vector,在插入完成后对每个vector统计。
具体操作步骤请查看代码注释。
#include<algorithm>//STL通用算法 #include<bitset>//STL位集容器 #include<cctype> #include<cmath> #include<complex> #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> #include<deque>//STL双端队列容器 #include<list>//STL线性列表容器 #include<map>//STL映射容器 #include<iostream> #include<queue>//STL队列容器 #include<set>//STL集合容器 #include<stack>//STL堆栈容器 #include<utility>//STL通用模板类 #include<vector>//STL动态数组容器 #define INF 0x3f3f3f3f #define ll long long using namespace std; typedef pair<int,int> pr; int ID,n,m,k,q,sum1[100002],sum2[100002]; int lnsum=0,mgsum,fa[100002]; int ans1[100002],ans2[100002]; struct uio{ int lx,ly,rx,ry; }prg[100002]; struct oiu{ int id,l,r; friend bool operator < (const oiu &x,const oiu &y) {return (x.l==y.l? x.r<y.r:x.l<y.l);} }; vector<oiu> row[100002],col[100002]; vector<pr> dsu[100002]; int find(int x) {return (x==fa[x]? x:fa[x]=find(fa[x]));} void merge(int x,int y) { int xx=find(x),yy=find(y); if(xx!=yy) fa[xx]=yy,mgsum++; } int main() { freopen("building.in","r",stdin); freopen("building.out","w",stdout); scanf("%d%d%d%d%d",&ID,&n,&m,&k,&q); for(int i=1;i<=k;i++) { int lx,ly,rx,ry; scanf("%d%d%d%d",&lx,&ly,&rx,&ry); prg[i]={lx,ly,rx,ry}; oiu lenx={i,lx,rx},leny={i,ly,ry}; row[lx].push_back(leny); col[ly].push_back(lenx); if(lx==rx) sum1[lx]+=(ry-ly+1);//行数量 else sum2[lx]++,sum2[rx+1]--;//列差分 } for(int i=1;i<=n;i++) sort(row[i].begin(),row[i].end()); for(int i=1;i<=m;i++) sort(col[i].begin(),col[i].end()); for(int i=1;i<=k;i++) { int dwn=prg[i].rx+1;//第i条的下面一行 if(dwn<=n&&row[dwn].size())//第i条的下面一行作为某些条的上边界 { oiu range={0,prg[i].ry,m+1};//第i条的右边 int tmp=upper_bound(row[dwn].begin(),row[dwn].end(),range) -row[dwn].begin()-1;//l<=ry的第一条 for(;tmp>=0&&row[dwn][tmp].r>=prg[i].ly;tmp--)//r>=ly dsu[dwn].push_back(make_pair(i,row[dwn][tmp].id));//在dwn行连接 } int rgt=prg[i].ry+1;//第i条的右边一列 if(rgt<=m&&col[rgt].size())//第i条的右边一列作为某些条的左边界 { oiu range={0,prg[i].rx,n+1};//第i条的下面 int tmp=upper_bound(col[rgt].begin(),col[rgt].end(),range) -col[rgt].begin()-1;//l<=rx的第一条 for(;tmp>=0&&col[rgt][tmp].r>=prg[i].lx;tmp--)//r>=lx dsu[max(prg[i].lx,prg[col[rgt][tmp].id].lx)].push_back(make_pair(i,col[rgt][tmp].id)); //在 第i条的上边界 与 该条的上边界 的靠下者 连接 } } for(int i=1;i<=n;i++) sum2[i]+=sum2[i-1];//差分->数量 for(int i=1;i<=n;i++) sum2[i]+=sum2[i-1],//数量->前缀和 sum1[i]+=sum1[i-1],//数量->前缀和 ans1[i]=sum1[i]+sum2[i];//第一种查询 for(int i=1;i<=k;i++) fa[i]=i; for(int i=1;i<=n;i++) { lnsum+=row[i].size();//第i行作为上边界的条的个数 for(int j=0;j<dsu[i].size();j++) merge(dsu[i][j].first,dsu[i][j].second);//在第i行合并的两条 ans2[i]=lnsum-mgsum;//总条数减去合并次数 } for(int i=1;i<=q;i++) { int u,v;scanf("%d%d",&u,&v); if(!u) printf("%d\n",ans1[v]); else printf("%d\n",ans2[v]); } return 0; }