nflsoi 7.29 题解
B.#15656 游乐园 / AT_abc216_e Amusement Park
题意
高桥君去游乐园玩了。
这个游乐园里有 NNN 个游乐设施,第 iii 个游乐设施的“乐趣”初始值为 AiA_iAi。当高桥君乘坐第 iii 个游乐设施时,会依次发生以下现象:
- 高桥君的“满足度”会增加第 iii 个游乐设施当前的“乐趣”值。
- 第 iii 个游乐设施的“乐趣”值减少 111。
高桥君的“满足度”初始值为 000。高桥君最多可以乘坐游乐设施 KKK 次。 请问高桥君最终能获得的“满足度”的最大值是多少?
注意,高桥君的“满足度”只会因为乘坐游乐设施而变化。
1≤N≤1051 \leq N \leq 10^51≤N≤105,1≤K≤2×1091 \leq K \leq 2 \times 10^91≤K≤2×109,1≤Ai≤2×1091 \leq A_i \leq 2 \times 10^91≤Ai≤2×109。
思路
想着用堆维护 kkk 次最优游乐设施,显然超时。这 kkk 真是大得离谱了。
我们考虑高桥玩的特征:玩满足度最大的。有可能有若干个满足度都是最大的,设从大到小排列 a1...ma_{1...m}a1...m 是 mmm 个最优项目,那么高桥会在 mmm 个之间交替游玩,直到这 mmm 个项目满足度降到 am+1a_{m+1}am+1,然后又在 m+1m+1m+1 个之间交替游玩……
那么枚举 i∈[1,n]i\in[1,n]i∈[1,n] 不仅是项目下标,还是交替游玩的项目数,对于前 iii 项游玩 i×(ai−ai+1)i\times (a_i-a_{i+1})i×(ai−ai+1) 次。这显然是最优方案,记 d=ai−ai+1d=a_i-a_{i+1}d=ai−ai+1,当交替游玩 iii 个项目为答案贡献了 i×∑t=ai+1+1ait=id(ai+1+1+ai)2i\times \displaystyle\sum_{t=a_{i+1}+1}^{a_i}t=\dfrac{id(a_{i+1}+1+a_i)}{2}i×t=ai+1+1∑ait=2id(ai+1+1+ai),用到了等差数列求和公式,次数记得减去 i×di\times di×d。
如果剩下的次数不够交替玩 i×di\times di×d 次,那就以玩 iii 次为周期,玩 cnt=⌊k/i⌋cnt=\left\lfloor k/i \right\rfloorcnt=⌊k/i⌋ 个周期,余数 ys=k−cnt×iys=k-cnt\times iys=k−cnt×i 也用来玩 ysysys 次满足度为 ai−cnta_i-cntai−cnt 的项目。依然等差数列求和。具体细节见代码。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+9;
ll n,k;
ll a[N];
bool cmp(ll x,ll y)
{return x>y;
}
int main()
{scanf("%lld%lld",&n,&k);for(int i=1;i<=n;i++)scanf("%lld",&a[i]);sort(a+1,a+n+1,cmp);ll ans=0;for(int i=1;i<=n;i++)//交替游玩项目数 {ll d=a[i]-a[i+1];//a[i]前全部交替游玩,直到玩到a[i+1] k-=d*i;if(k<0){k+=d*i;ll cnt=k/i,ys=k%i;ans+=i*(a[i]+a[i]-cnt+1)*cnt/2+ys*(a[i]-cnt);//用不完全部 break;}ans+=i*(a[i+1]+1+a[i])*d/2;//首项 a[i+1]+1 末项a[i] }printf("%lld",ans);return 0;
}
C.#16007 抽卡
题意
思路
模拟题而已,考察 STL 的应用。用 set
维护堆顶以及 upper_bound
查询操作,vector
维护每个牌堆,因为 vector
非常灵活,可以用 move()
实现数据的迁移。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+9;
int n,k;
set<int>S;//堆顶
map<int,vector<int> >heap;//堆顶与堆
int ans[N];
int main()
{scanf("%d%d",&n,&k);for(int i=1;i<=n;i++){int x;scanf("%d",&x);set<int>::iterator it=S.upper_bound(x);if(it==S.end()){if(k==1){ans[x]=i;continue;}S.insert(x);heap[x].push_back(x);}else {int lastop=*it;S.erase(lastop);vector<int>&tem=heap[lastop];//防止每次开一个vector,不然会 MLEtem.push_back(x);if(tem.size()==k){for(auto t:tem)ans[t]=i;heap.erase(lastop);}else {S.insert(x);heap[x]=move(tem);}}}for(int i=1;i<=n;i++){if(ans[i]==0)puts("-1");else printf("%d\n",ans[i]);}return 0;
}
D.#16016 位运算操作 / AT_abc261_e Many Operations
题意
有一个变量 XXX,以及 NNN 种可以改变 XXX 值的操作。第 iii 个操作由整数对 (Ti,Ai)(T_i, A_i)(Ti,Ai) 表示,含义如下:
- 当 Ti=1T_i=1Ti=1 时,将 XXX 的值替换为 XandAiX\ \mathrm{and}\ A_iX and Ai。
- 当 Ti=2T_i=2Ti=2 时,将 XXX 的值替换为 XorAiX\ \mathrm{or}\ A_iX or Ai。
- 当 Ti=3T_i=3Ti=3 时,将 XXX 的值替换为 XxorAiX\ \mathrm{xor}\ A_iX xor Ai。
请从变量 XXX 被初始化为值 CCC 的状态开始,依次执行以下操作:
- 执行操作 111,输出操作后的 XXX 的值。
- 接着,依次执行操作 1,21,21,2,输出操作后的 XXX 的值。
- 接着,依次执行操作 1,2,31,2,31,2,3,输出操作后的 XXX 的值。
- ⋮\vdots⋮
- 接着,依次执行操作 1,2,…,N1,2,\ldots,N1,2,…,N,输出操作后的 XXX 的值。
1≤N≤2×1051 \leq N \leq 2\times 10^51≤N≤2×105,1≤Ti≤31 \leq T_i \leq 31≤Ti≤3,0≤Ai<2300 \leq A_i < 2^{30}0≤Ai<230,0≤C<2300 \leq C < 2^{30}0≤C<230。
思路
操作次数超级多,而且位运算没有交换律。
看到范围,C,AiC,A_iC,Ai 再大二进制顶天就 303030 位,于是考虑拆位计算每一位。
三种运算都有一些性质,对于一个二进制位 x∈{0,1}x\in\{0,1\}x∈{0,1}:
- xand0=0x \ \mathrm{and}\ 0=0x and 0=0,xand1x \ \mathrm{and}\ 1x and 1 无事发生;
- xor1=1x\ \mathrm{or}\ 1=1x or 1=1,xor0x\ \mathrm{or}\ 0x or 0 无事发生;
- xxor0x\ \mathrm{xor}\ 0x xor 0 无事发生,xxor1x\ \mathrm{xor}\ 1x xor 1 就反转。
枚举二进制位 t∈[0,30]t\in[0,30]t∈[0,30],记 curcurcur 为当前 CCC 的 ttt 位。枚举 iii 表示以第 iii 次操作结尾,记 xxx 位 aia_iai 的 ttt 位。
- 当 op=1op=1op=1 为 and\mathrm{and}and 操作,若 x=0x=0x=0 则每到这次操作 ttt 位都变成 alt=0alt=0alt=0;
- 当 op=2op=2op=2 为 or\mathrm{or}or 操作,若 x=1x=1x=1 则每到这次操作 ttt 位都变成 alt=1alt=1alt=1;
- 当 op=3op=3op=3 位 xor\mathrm{xor}xor 操作,若 x=1x=1x=1 则每到这次操作都要数位反转,xortot+1xortot+1xortot+1。
上文的 altaltalt 和 xortotxortotxortot 相当于两个 tagtagtag,虽然以第 iii 步结尾,前面还有很多步没看,但是做到这步答案都是固定的——不论前面的操作如何,做到这步,该位必然是 altaltalt。
初始时 alt=−1alt=-1alt=−1,如果 altaltalt 没有被修改就不管它,表示前面没有可以影响 curcurcur 的 op=1/2op=1/2op=1/2 操作;否则直接把 altaltalt 赋值到 curcurcur 上。
如果 xortotxortotxortot 为奇数,说明 curcurcur 位要被反转。同时反转后不清空 xortotxortotxortot,因为后面的某一个结尾出现 xor1\mathrm{xor}\ 1xor 1 时,前面被反转了奇数次,加上这次就偶数次,相当于没影响。
然后根据 curcurcur 是否为 111 为 ansians_iansi 的 ttt 位决定填 111 还是填 000,ansians_iansi 表示以操作 iii 为结尾的答案。
感觉这个做法很神仙(玄学)!这种奇妙的更新题也就 AT 能出了 qwq。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2e5+9;
ll n,c;
ll op[N],a[N];
ll ans[N];
int main()
{scanf("%lld%lld",&n,&c);for(int i=1;i<=n;i++)scanf("%lld%lld",&op[i],&a[i]);for(ll t=30;t>=0;t--){bool cur=(c&(1<<t));ll alt=-1,xortot=0;for(ll i=1;i<=n;i++)//第i操作结尾 {bool x=(a[i]&(1<<t));//做到这步,该位必然是altif(op[i]==1){cur&=x;if(x==0)alt=x,xortot=0;}if(op[i]==2){cur|=x;if(x==1)alt=x,xortot=0;}if(op[i]==3)xortot+=x;if(alt!=-1)cur=alt;//不论前面的操作如何,到这里都是alt if(xortot&1)cur^=1;if(cur)ans[i]+=(1<<t);}}for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);return 0;
}
E.#16313 纵横权重和 / AT_abc298_f Rook Score
我的博客
F.#9160 写诗 / 洛谷 P5196 USACO19JAN Cow Poetry G
题意
题目传送门,建议去仔细读懂题意和样例解释。
思路
音节即长度。因为韵部只是对一串单词的最后一个有要求,前面可以乱排。设 gig_igi 表示乱排组成 iii 个音节的方案数,容易完全背包转移:
g[0]=1;//初始化
for(int i=1;i<=k;i++)
for(int j=1;j<=n;j++)//单词下标
if(i>=len[j])g[i]=(g[i]+g[i-len[j]])%mod;
单词有 nnn 个,韵部最多 nnn 种,我们用 nnn 个动态数组 YiY_iYi 存储韵部为 iii 的单词长度集合。设 fif_ifi 表示韵脚为 iii 的方案数,枚举韵脚 iii,对于一个韵部为 iii、长度为 lenlenlen 的单词:
fi←gk−lenf_i\leftarrow g_{k-len}fi←gk−len
规定的那个模式串其实没什么用,只需要统计每个字母的个数 cnticnt_{i}cnti,i∈[A(1),Z(26)]i\in[A(1),Z(26)]i∈[A(1),Z(26)]。分别枚举押韵模式和韵脚,使二者配对,然后计算 ∑i=126∑j=1nfjcnti\displaystyle\sum_{i=1}^{26}\sum_{j=1}^n f_j^{cnt_i}i=1∑26j=1∑nfjcnti 即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5009,mod=1e9+7;
int n,m,k;
int len[N],yun[N];
char e[N];
ll f[N],g[N];
//f(y):韵脚为y方案数
//g(i):i个音节方案数
int cnt[33];
ll qpow(ll x,ll k)
{ll ret=1;while(k){if(k&1)ret=ret*x%mod;x=x*x%mod;k>>=1;}return ret;
}
vector<ll>Y[N];
int main()
{scanf("%d%d%d",&n,&m,&k);for(int i=1;i<=n;i++){scanf("%d%d",&len[i],&yun[i]);Y[yun[i]].push_back(len[i]);}g[0]=1;for(int i=1;i<=k;i++)for(int j=1;j<=n;j++)//单词下标 if(i>=len[j])g[i]=(g[i]+g[i-len[j]])%mod;for(int i=1;i<=n;i++)//韵脚 for(auto x:Y[i])f[i]=(f[i]+g[k-x])%mod;//前面单词乱拍,最后一个制定韵脚为i for(int i=1;i<=m;i++){char c;cin>>c;cnt[c-'A'+1]++;}ll ans=1;for(int i=1;i<=26;i++)//单句的模式 {if(!cnt[i])continue; ll sum=0;for(int j=1;j<=n;j++)//韵部 sum=(sum+qpow(f[j],cnt[i]))%mod;ans=ans*sum%mod;}printf("%lld",ans);return 0;
}
G.#9171 涂抹油漆 / 洛谷 P6100 USACO19FEB Painting the Barn G
题意
我们将谷仓的墙描述为一个 X-Y 平面,每次涂油漆的区域都是一个矩形。FJ 在这个平面上绘制了 NNN 个矩形,每个矩形的边均与坐标轴平行。因此我们用矩形的左下角和右上角坐标来描述一个矩形。
FJ 想在谷仓里涂几层油漆,这样就不需要在不久的将来再次重新涂油漆。但是,他不想浪费时间涂过多的油漆。事实证明,KKK 层涂料是最佳用量。但是因为涂油漆的面积太小了,FJ 并不太高兴。他决定最多再绘制两个不相交的矩形(这里的相交指两个矩形交的面积大于零,即如果两个矩形仅共用一条边或一个点,则不视为相交)来增加面积。当然不绘制新矩形或仅绘制一个新矩形也是允许的。
1≤N,K≤1051 \leq N,K \leq 10^51≤N,K≤105,0≤x1,y1,x2,y2≤2000 \leq x_1,y_1,x_2,y_2 \leq 2000≤x1,y1,x2,y2≤200。
思路
只有被涂了 k−1k-1k−1 和 kkk 次的矩形会影响贡献。根据本题弱化版我们使用二维查分计算每个格子被涂了几次(代码坐标 +1+1+1 避开 000):
for(int i=1;i<=n;i++)
{ll xa,ya,xb,yb;scanf("%lld%lld%lld%lld",&xa,&ya,&xb,&yb);a[xa+1][ya+1]++,a[xb+1][yb+1]++;a[xb+1][ya+1]--,a[xa+1][yb+1]--;
}
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
在已经填了 k−1k-1k−1 次的格子上再涂抹会产生 +1+1+1 贡献;在已经填了 kkk 次的格子上再涂抹会产生 −1-1−1 贡献。
设 fi,jf_{i,j}fi,j 表示以 (i,j)(i,j)(i,j) 为右下角的最大贡献,gi,jg_{i,j}gi,j 表示以 (i,j)(i,j)(i,j) 为左上角的最大贡献。那么我们要找一个包含 (i,j)(i,j)(i,j) 的子矩阵。
子矩阵问题考虑枚举行或者列的上下界,然后压成一个一维数组找,计算当前 ax,ija_{x,i~j}ax,i j 的前缀和 sxs_xsx,记 sss 数组的前缀最小值 mnmnmn,那么 fx,j=max{sx−mn}f_{x,j}=\max\{s_x-mn\}fx,j=max{sx−mn};反过来处理 gx,ig_{x,i}gx,i 同理。下面的枚举了列的上下界(也不记得为什么了,本人的习惯是枚举行的上下界)。
memset(f,-inf,sizeof(f));//不能只赋0
memset(g,-inf,sizeof(g));
for(int i=1;i<=N;i++)
{for(int j=i;j<=N;j++){ll mn=0;for(int x=1;x<=N;x++){s[x]=s[x-1]+cal(x,i,x,j);f[x][j]=max(f[x][j],s[x]-mn);mn=min(s[x],mn);}mn=0;for(int x=N;x>=1;x--){s[x]=s[x+1]+cal(x,i,x,j);g[x][i]=max(g[x][i],s[x]-mn);mn=min(s[x],mn);}}
}
最后计算 (1,1)−(i,j)(1,1)-(i,j)(1,1)−(i,j) 内的最大贡献矩形 Fi,jF_{i,j}Fi,j 和 (i,j)−(n,m)(i,j)-(n,m)(i,j)−(n,m) 内的最大贡献矩形 Gi,jG_{i,j}Gi,j。然后枚举将矩形分开两部分的直线 x=ix=ix=i 或者 y=iy=iy=i,计算 max(max{y=i∣FN,i−1+G1,i},max{x=i∣Fi−1,N+Gi,1})\max(\max\{y=i|F_{N,i-1+G_{1,i}}\},\max\{x=i|F_{i-1,N}+G_{i,1}\})max(max{y=i∣FN,i−1+G1,i},max{x=i∣Fi−1,N+Gi,1})。
代码
//for test
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=200,inf=0x3f3f3f3f;
ll n,k;
ll a[N+3][N+3],b[N+3][N+3],sum[N+3][N+3],s[N+3];
ll f[N+3][N+3],g[N+3][N+3],F[N+3][N+3],G[N+3][N+3];
ll cntk,ans;
ll cal(ll xa,ll ya,ll xb,ll yb)
{return sum[xb][yb]-sum[xa-1][yb]-sum[xb][ya-1]+sum[xa-1][ya-1];
}
int main()
{
// freopen("P6100_2.in","r",stdin);scanf("%lld%lld",&n,&k);for(int i=1;i<=n;i++){ll xa,ya,xb,yb;scanf("%lld%lld%lld%lld",&xa,&ya,&xb,&yb);a[xa+1][ya+1]++,a[xb+1][yb+1]++;a[xb+1][ya+1]--,a[xa+1][yb+1]--;}for(int i=1;i<=N;i++)for(int j=1;j<=N;j++)sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];for(int i=1;i<=N;i++){for(int j=1;j<=N;j++){if(sum[i][j]==k-1)a[i][j]=1;else if(sum[i][j]==k)cntk++,a[i][j]=-1;else a[i][j]=0;}}for(int i=1;i<=N;i++)for(int j=1;j<=N;j++)sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];memset(f,-inf,sizeof(f));memset(g,-inf,sizeof(g));for(int i=1;i<=N;i++){for(int j=i;j<=N;j++){ll mn=0;for(int x=1;x<=N;x++){s[x]=s[x-1]+cal(x,i,x,j);f[x][j]=max(f[x][j],s[x]-mn);mn=min(s[x],mn);}mn=0;for(int x=N;x>=1;x--){s[x]=s[x+1]+cal(x,i,x,j);g[x][i]=max(g[x][i],s[x]-mn);mn=min(s[x],mn);}}}memset(F,-inf,sizeof(F));memset(G,-inf,sizeof(G));for(int i=1;i<=N;i++)for(int j=1;j<=N;j++)F[i][j]=max(max(F[i][j],f[i][j]),max(F[i-1][j],F[i][j-1]));for(int i=N;i>=1;i--)for(int j=N;j>=1;j--)G[i][j]=max(max(G[i][j],g[i][j]),max(G[i+1][j],G[i][j+1]));for(int i=2;i<=N;i++)ans=max(ans,max(F[N][i-1]+G[1][i],F[i-1][N]+G[i][1]));printf("%lld",ans+cntk);return 0;
}