nflsoi 7.29 题解
高桥君去游乐园玩了。这个游乐园里有N个游乐设施,第i个游乐设施的“乐趣”初始值为Ai。当高桥君乘坐第iii1高桥君的“满足度”初始值为0。高桥君最多可以乘坐游乐设施K次。请问高桥君最终能获得的“满足度”的最大值是多少?注意,高桥君的“满足度”只会因为乘坐游乐设施而变化。
B.#15656 游乐园 / AT_abc216_e Amusement Park
题意
高桥君去游乐园玩了。
这个游乐园里有 N N N 个游乐设施,第 i i i 个游乐设施的“乐趣”初始值为 A i A_i Ai。当高桥君乘坐第 i i i 个游乐设施时,会依次发生以下现象:
- 高桥君的“满足度”会增加第 i i i 个游乐设施当前的“乐趣”值。
- 第 i i i 个游乐设施的“乐趣”值减少 1 1 1。
高桥君的“满足度”初始值为 0 0 0。高桥君最多可以乘坐游乐设施 K K K 次。 请问高桥君最终能获得的“满足度”的最大值是多少?
注意,高桥君的“满足度”只会因为乘坐游乐设施而变化。
1 ≤ N ≤ 10 5 1 \leq N \leq 10^5 1≤N≤105, 1 ≤ K ≤ 2 × 10 9 1 \leq K \leq 2 \times 10^9 1≤K≤2×109, 1 ≤ A i ≤ 2 × 10 9 1 \leq A_i \leq 2 \times 10^9 1≤Ai≤2×109。
思路
想着用堆维护 k k k 次最优游乐设施,显然超时。这 k k k 真是大得离谱了。
我们考虑高桥玩的特征:玩满足度最大的。有可能有若干个满足度都是最大的,设从大到小排列 a 1... m a_{1...m} a1...m 是 m m m 个最优项目,那么高桥会在 m m m 个之间交替游玩,直到这 m m m 个项目满足度降到 a m + 1 a_{m+1} am+1,然后又在 m + 1 m+1 m+1 个之间交替游玩……
那么枚举 i ∈ [ 1 , n ] i\in[1,n] i∈[1,n] 不仅是项目下标,还是交替游玩的项目数,对于前 i i i 项游玩 i × ( a i − a i + 1 ) i\times (a_i-a_{i+1}) i×(ai−ai+1) 次。这显然是最优方案,记 d = a i − a i + 1 d=a_i-a_{i+1} d=ai−ai+1,当交替游玩 i i i 个项目为答案贡献了 i × ∑ t = a i + 1 + 1 a i t = i d ( a i + 1 + 1 + a i ) 2 i\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 × d i\times d i×d。
如果剩下的次数不够交替玩 i × d i\times d i×d 次,那就以玩 i i i 次为周期,玩 c n t = ⌊ k / i ⌋ cnt=\left\lfloor k/i \right\rfloor cnt=⌊k/i⌋ 个周期,余数 y s = k − c n t × i ys=k-cnt\times i ys=k−cnt×i 也用来玩 y s ys ys 次满足度为 a i − c n t a_i-cnt ai−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 查询操作,map 套 vector 维护每个牌堆堆顶和牌,因为 map 和 vector 这些 STL 非常灵活,可以用 move() 实现数据的迁移和 erase 实现删除。
代码
#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,不然会 MLE
tem.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
题意
有一个变量 X X X,以及 N N N 种可以改变 X X X 值的操作。第 i i i 个操作由整数对 ( T i , A i ) (T_i, A_i) (Ti,Ai) 表示,含义如下:
- 当 T i = 1 T_i=1 Ti=1 时,将 X X X 的值替换为 X a n d A i X\ \mathrm{and}\ A_i X and Ai。
- 当 T i = 2 T_i=2 Ti=2 时,将 X X X 的值替换为 X o r A i X\ \mathrm{or}\ A_i X or Ai。
- 当 T i = 3 T_i=3 Ti=3 时,将 X X X 的值替换为 X x o r A i X\ \mathrm{xor}\ A_i X xor Ai。
请从变量 X X X 被初始化为值 C C C 的状态开始,依次执行以下操作:
- 执行操作 1 1 1,输出操作后的 X X X 的值。
- 接着,依次执行操作 1 , 2 1,2 1,2,输出操作后的 X X X 的值。
- 接着,依次执行操作 1 , 2 , 3 1,2,3 1,2,3,输出操作后的 X X X 的值。
- ⋮ \vdots ⋮
- 接着,依次执行操作 1 , 2 , … , N 1,2,\ldots,N 1,2,…,N,输出操作后的 X X X 的值。
1 ≤ N ≤ 2 × 10 5 1 \leq N \leq 2\times 10^5 1≤N≤2×105, 1 ≤ T i ≤ 3 1 \leq T_i \leq 3 1≤Ti≤3, 0 ≤ A i < 2 30 0 \leq A_i < 2^{30} 0≤Ai<230, 0 ≤ C < 2 30 0 \leq C < 2^{30} 0≤C<230。
思路
操作次数超级多,而且位运算没有交换律。
看到范围, C , A i C,A_i C,Ai 再大二进制顶天就 30 30 30 位,于是考虑拆位计算每一位。
三种运算都有一些性质,对于一个二进制位 x ∈ { 0 , 1 } x\in\{0,1\} x∈{0,1}:
- x a n d 0 = 0 x \ \mathrm{and}\ 0=0 x and 0=0, x a n d 1 x \ \mathrm{and}\ 1 x and 1 无事发生;
- x o r 1 = 1 x\ \mathrm{or}\ 1=1 x or 1=1, x o r 0 x\ \mathrm{or}\ 0 x or 0 无事发生;
- x x o r 0 x\ \mathrm{xor}\ 0 x xor 0 无事发生, x x o r 1 x\ \mathrm{xor}\ 1 x xor 1 就反转。
枚举二进制位 t ∈ [ 0 , 30 ] t\in[0,30] t∈[0,30],记 c u r cur cur 为当前 C C C 的 t t t 位。枚举 i i i 表示以第 i i i 次操作结尾,记 x x x 位 a i a_i ai 的 t t t 位。
- 当 o p = 1 op=1 op=1 为 a n d \mathrm{and} and 操作,若 x = 0 x=0 x=0 则每到这次操作 t t t 位都变成 a l t = 0 alt=0 alt=0;
- 当 o p = 2 op=2 op=2 为 o r \mathrm{or} or 操作,若 x = 1 x=1 x=1 则每到这次操作 t t t 位都变成 a l t = 1 alt=1 alt=1;
- 当 o p = 3 op=3 op=3 位 x o r \mathrm{xor} xor 操作,若 x = 1 x=1 x=1 则每到这次操作都要数位反转, x o r t o t + 1 xortot+1 xortot+1。
上文的 a l t alt alt 和 x o r t o t xortot xortot 相当于两个 t a g tag tag,虽然以第 i i i 步结尾,前面还有很多步没看,但是做到这步答案都是固定的——不论前面的操作如何,做到这步,该位必然是 a l t alt alt。
初始时 a l t = − 1 alt=-1 alt=−1,如果 a l t alt alt 没有被修改就不管它,表示前面没有可以影响 c u r cur cur 的 o p = 1 / 2 op=1/2 op=1/2 操作;否则直接把 a l t alt alt 赋值到 c u r cur cur 上。
如果 x o r t o t xortot xortot 为奇数,说明 c u r cur cur 位要被反转。同时反转后不清空 x o r t o t xortot xortot,因为后面的某一个结尾出现 x o r 1 \mathrm{xor}\ 1 xor 1 时,前面被反转了奇数次,加上这次就偶数次,相当于没影响。
然后根据 c u r cur cur 是否为 1 1 1 为 a n s i ans_i ansi 的 t t t 位决定填 1 1 1 还是填 0 0 0, a n s i ans_i ansi 表示以操作 i i i 为结尾的答案。
感觉这个做法很神仙(玄学)!这种奇妙的更新题也就 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));//做到这步,该位必然是alt
if(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
题意
题目传送门,建议去仔细读懂题意和样例解释。
思路
音节即长度。因为韵部只是对一串单词的最后一个有要求,前面可以乱排。设 g i g_i gi 表示乱排组成 i i 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;
(此处是乱排,词语顺序改变算作不同方案,故先枚举背包,不计算顺序变化详见这里)
单词有 n n n 个,韵部最多 n n n 种,我们用 n n n 个动态数组 Y i Y_i Yi 存储韵部为 i i i 的单词长度集合。设 f i f_i fi 表示韵脚为 i i i 的方案数,枚举韵脚 i i i,对于一个韵部为 i i i、长度为 l e n len len 的单词:
f i ← g k − l e n f_i\leftarrow g_{k-len} fi←gk−len
规定的那个模式串其实没什么用,只需要统计每个字母的个数 c n t i cnt_{i} cnti, i ∈ [ A ( 1 ) , Z ( 26 ) ] i\in[A(1),Z(26)] i∈[A(1),Z(26)]。分别枚举押韵模式和韵脚,使二者配对,然后计算 ∑ i = 1 26 ∑ j = 1 n f j c n t i \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 在这个平面上绘制了 N N N 个矩形,每个矩形的边均与坐标轴平行。因此我们用矩形的左下角和右上角坐标来描述一个矩形。
FJ 想在谷仓里涂几层油漆,这样就不需要在不久的将来再次重新涂油漆。但是,他不想浪费时间涂过多的油漆。事实证明, K K K 层涂料是最佳用量。但是因为涂油漆的面积太小了,FJ 并不太高兴。他决定最多再绘制两个不相交的矩形(这里的相交指两个矩形交的面积大于零,即如果两个矩形仅共用一条边或一个点,则不视为相交)来增加面积。当然不绘制新矩形或仅绘制一个新矩形也是允许的。
1 ≤ N , K ≤ 10 5 1 \leq N,K \leq 10^5 1≤N,K≤105, 0 ≤ x 1 , y 1 , x 2 , y 2 ≤ 200 0 \leq x_1,y_1,x_2,y_2 \leq 200 0≤x1,y1,x2,y2≤200。
思路
只有被涂了 k − 1 k-1 k−1 和 k k k 次的矩形会影响贡献。根据本题弱化版我们使用二维查分计算每个格子被涂了几次(代码坐标 + 1 +1 +1 避开 0 0 0):
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 − 1 k-1 k−1 次的格子上再涂抹会产生 + 1 +1 +1 贡献;在已经填了 k k k 次的格子上再涂抹会产生 − 1 -1 −1 贡献。
设 f i , j f_{i,j} fi,j 表示以 ( i , j ) (i,j) (i,j) 为右下角的最大贡献, g i , j g_{i,j} gi,j 表示以 ( i , j ) (i,j) (i,j) 为左上角的最大贡献。那么我们要找一个包含 ( i , j ) (i,j) (i,j) 的子矩阵。
子矩阵问题考虑枚举行或者列的上下界,然后压成一个一维数组找,计算当前 a x , i j a_{x,i~j} ax,i j 的前缀和 s x s_x sx,记 s s s 数组的前缀最小值 m n mn mn,那么 f x , j = max { s x − m n } f_{x,j}=\max\{s_x-mn\} fx,j=max{sx−mn};反过来处理 g x , i g_{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) 内的最大贡献矩形 F i , j F_{i,j} Fi,j 和 ( i , j ) − ( n , m ) (i,j)-(n,m) (i,j)−(n,m) 内的最大贡献矩形 G i , j G_{i,j} Gi,j。然后枚举将矩形分开两部分的直线 x = i x=i x=i 或者 y = i y=i y=i,计算 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|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;
}
更多推荐


所有评论(0)