感想
我纯废物
本场的题并非特别难
只是自己码力不及
题面:Link
T1
题面
有一个有 \(m\) 个格子的表格和 \(n\) 个下标
表格初始为空
从前到后顺次将这 \(n\) 个下标对应的表格位置涂上颜色
如果当前下标对应的位置已被涂色
就一直向前找到空位并涂色
设第 \(i\) 个下标涂色位置为 \(pos_i\)
如果没有空位,则将 \(pos_i\) 记作 \(0\)
输出 \((1*pos_1+2*pos_2+...+n*pos_n)\%1000000007\) 的值
\(1<=N,M<=3000000\)
解析
这题场切了
首先容易想到 \(O(n\log^2{n})\) 的做法:树状数组+二分
但这样会 T
想到之前做根号线段树的并查集优化
发现这题也可以这样弄
然后就可以了
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3000009;
const ll Mod=1000000007;
int n,m,f[N];
ll ans=0;
int F(int i){
if(f[i]==i)return i;
return f[i]=F(f[i]);
}
int main(){
int tmp;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)f[i]=i;
for(int i=1;i<=n;i++){
scanf("%d",&tmp);
tmp=F(tmp);
if(tmp<1)continue;
f[tmp]=tmp-1;
ans=(ans+(ll)i*tmp)%Mod;
}
printf("%lld\n",ans);
return 0;
}
T2
题面
将一颗树上的每个节点共染上 \(M\) 种颜色
将同种颜色的点之间的路径上的边边权 \(+1\)
构造一种染色方案使边权大于 \(K\) 的边数刚好为 \(S\)
如无解,输出 \(No\)
解析
本题是全场最难题目
我们遍历每一条边
Code
T3
题面
给出一个只由 \(ABCD\) 组成的字符串 \(S\)
要求实现两种操作
- \(1\ L\ R\)
对于 \([L,R]\) 这一段字符串
将 \(A\) 变成 \(B\) ,将 \(B\) 变成 \(C\) ,将 \(C\) 变成 \(D\) ,将 \(D\) 变成 \(A\)。 - \(2\ L\ R\ K\)
求将 \([L,R]\) 这一段字符串划分成 \(K\) 段字符串,且其中字符的字典序单调上升的总方案数
例如 \(AB\ ABC\ CD\ D\ AC\) 等都是合法的字符串
将方案数 \(\mod{998244353}\) 输出
解析
本题作者场上题目看错了直接空着
看到区间操作能想到线段树
思考第二部分怎么求
我们发现对于一个字符串
有的部位是必须进行分割的
如 \(ACBDAA\)
其中 \(CB\ DA\ AA\) 处都必须进行分割
我们设这样的位置有 \(Cut\) 处
题目说我们要分 \(K\) 段
其实就是要让我们找 \(K-1\) 个分割点
实际上我们能定的只有 \(K-Cut-1\) 处
而一共可选的呢? \(R-L+1-1-Cut=R-L+Cut\)
所以我们组合数学
阶乘我们直接算显然就 T 了
所以我们预处理出每一个阶乘
然后 \(exGCD\) / 费马小定理求逆元即可
所以我们需要做的就是求区间 \(Cut\)
有一个暴力思路是直接维护区间 \(AA\ AB\ AC\ ......\) 的个数
然后还需要维护每段区间的左右端点以处理区间合并问题
然而并没有不暴力的思路
然后好像就可以了
Code
#include<bits/stdc++.h>
#define install int ls=i<<1,rs=i<<1|1,mid=(l+r)>>1
#define partall int ls=i<<1,rs=i<<1|1
using namespace std;
typedef long long ll;
const int N=100009;
const ll M=998244353;
//Scan & Pre Part
int n,T;
char k[N];
ll Shit[N];
void Scan(){
scanf("%d%d%s",&n,&T,k+1),Shit[0]=1;
for(int i=1;i<=n;i++)Shit[i]=(Shit[i-1]*i)%M;
}
//ExGCD Part
//ax % M = 1
//ax+bM=GCD(a,b)
//ax+by=GCD(a,b)=GCD(b,a%b)=bX+(a-a/b*b)Y=aY+b(X-a/b*Y)
void ExGCD(ll a,ll b,ll &x,ll &y){
if(b==0){x=1,y=0;return;}
ExGCD(b,a%b,y,x),y-=a/b*x;
}
int Inv(int i){
ll x,y;
ExGCD(Shit[i],M,x,y);
return (x%M+M)%M;
}
class Segment{
public:
void Build(int i=1,int l=1,int r=n){
if(l==r){lim[i]=rim[i]=k[l]-'A';return;}
install;Build(ls,l,mid),Build(rs,mid+1,r);
Pu(i);
}
void Modify(int i,int l,int r,int L,int R){
if(l==L&&r==R){Ad(i,1),add[i]++;return;}
Pd(i,l,r);
install;
if(R<=mid)Modify(ls,l,mid,L,R);
else if(L>mid)Modify(rs,mid+1,r,L,R);
else Modify(ls,l,mid,L,mid),Modify(rs,mid+1,r,mid+1,R);
Pu(i);
}
int Query(int i,int l,int r,int L,int R){
if(l==L&&r==R)return Cut(i);
Pd(i,l,r);
install;
if(R<=mid)return Query(ls,l,mid,L,R);
else if(L>mid) return Query(rs,mid+1,r,L,R);
else return Query(ls,l,mid,L,mid)+Query(rs,mid+1,r,mid+1,R)+((rim[ls]>=lim[rs])?1:0);
}
void Show(int i=1,int l=1,int r=n){
for(int it=l;it<=r;it++)putchar(k[it]);
printf(" [%c,%c]%d\n",lim[i]+'A',rim[i]+'A',Cut(i));
if(l==r){return;}
Pd(i,l,r);
install;
Show(ls,l,mid),Show(rs,mid+1,r);
}
private:
int tag[N<<2][4][4],lim[N<<2],rim[N<<2],add[N<<2];
//A0 B1 C2 D3
void Pu(int i){
partall;
for(int x=0;x<4;x++)for(int y=0;y<4;y++)tag[i][x][y]=tag[ls][x][y]+tag[rs][x][y];
tag[i][rim[ls]][lim[rs]]++;
lim[i]=lim[ls],rim[i]=rim[rs];
}
void Ad(int i,int d){
int tmp[4][4];
for(int x=0;x<=3;x++)for(int y=0;y<=3;y++)tmp[x][y]=tag[i][x][y];
for(int x=0;x<=3;x++)for(int y=0;y<=3;y++)tag[i][(x+d)%4][(y+d)%4]=tmp[x][y];
lim[i]=(lim[i]+d)%4,rim[i]=(rim[i]+d)%4;
}
void Pd(int i,int l,int r){
if(!add[i]||l==r)return;
partall;
add[ls]+=add[i],add[rs]+=add[i],Ad(ls,add[i]),Ad(rs,add[i]),add[i]=0;
}
int Cut(int i){int ans=0;for(int x=0;x<4;x++)for(int y=0;y<=x;y++)ans+=tag[i][x][y];return ans;}
}t;
int main(){
Scan(),t.Build();
int op,l,r,d,Cut,mm,nn;
while(T--){
// t.Show();
// puts("---------------------------");
scanf("%d%d%d",&op,&l,&r);
if(op==1){
t.Modify(1,1,n,l,r);
}else{
scanf("%d",&d);
Cut=t.Query(1,1,n,l,r);
mm=d-Cut-1,nn=r-l-Cut;
if(mm>nn)puts("0");
else printf("%lld\n",Shit[nn]*Inv(mm)%M*Inv(nn-mm)%M);
//printf("C(%d,%d)(%d)=%lld\n",mm,nn,Cut,Shit[nn]*Inv(mm)%M*Inv(nn-mm)%M);
}
}
}
T4
题面
给定一个容量为 \(M\) 的背包和 \(N\) 种物品 \((1<=N,M<=3000)\)
每种物品都有无限个
每个物品有两个属性 \(A_i\) 和 \(B_i\)
物品的价值定义为 \(\alpha_j*A_i\ (1<=j<=10)\)
其中满足 \(\frac{j-1}{10}<\frac{B_i}{A_i}<=\frac{j}{10}\)
\(\alpha_1\) 到 \(\alpha_{10}\) 会提前给出,且我们保证 \(\alpha_{i-1}<=\alpha_i<=\alpha_{i+1}\)
我们可以合并两个物品 \(Item_i\) 和 \(Item_j\)
得到的物品 \(Item_k\) 满足 \(A_k=A_i+A_j\) 且 \(B_k=B_i+B_j\)
你可以随意合并物品
求最终背包能够装下的最大价值
解析
这题听完 TJ 极为简单
要不我纯废物呢
这题看这个题面是很裸的背包
而且我们发现一个性质:需要让 \(\alpha\) 的等级尽量高
而其实一个物品说白了只有两维:等级 \(\alpha\) 和质量 \(A\)
所以我们润题解思考得出可以设一维质量求能获得的最高等级
继而确定其价值然后再完全背包求可以了
具体过程:
设 \(F_i\) 为在 \(\sum{A}\) 为 \(i\) 的时候能获得的最大的 \(\sum{B}\)
易得出 \(F_i=max(F_i,F_{i-A_j}+B_j)\)
接下来将每一个 \(F_i\) 看作一样物品
跑完全背包即可
时间复杂度 \(O(nm+m^2)\)
关于完全背包
朴素完全背包 (时空 \(O(n^3)\))
int dp[][],val[],weight[]
for i 1->n
for j weight[i]->m
dp[i][j]=dp[i-1][j]
for k k*weight[i]<=j
dp[i][j]=max(dp[i][j],dp[i-1][j-weight[i]*k]+val[i]*k)
空间优化完全背包 (时 \(O(n^3)\) 空 \(O(n^2)\))
int dp[],val[],weight[]
for i 1->n
for j m->weight[i]
for k k*weight[i]<=j
dp[j]=max(dp[j],dp[j-weight[i]*k]+val[i]*k)
时空优化完全背包(时空 \(O(n^2)\))
int dp[],val[],weight[]
for i 1->n
for j weight[i]->m
dp[j]=max(dp[j],dp[j-weight[i]]+val[i])
Code
#include<bits/stdc++.h>
using namespace std;
const int N=3024;
int A[N],B[N],F[N],DP[N],val[13];
int alpha(int a,int b){
if(a==0)return 0;
for(int i=1;i<=10;i++)if(a*10>(i-1)*b&&10*a<=i*b)return val[i];
puts("Shit"),exit(0);
}
int main(){
int T,n,m;
scanf("%d",&T);
while(T--){
for(int i=1;i<=10;i++)scanf("%d",&val[i]);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)F[i]=-1e8-99,DP[i]=0;
for(int i=1;i<=n;i++)scanf("%d%d",&A[i],&B[i]);
for(int i=1;i<=n;i++)for(int j=A[i];j<=m;j++)F[j]=max(F[j],F[j-A[i]]+B[i]);
for(int i=1;i<=m;i++)for(int j=i;j<=m;j++)if(F[i]>0)DP[j]=max(DP[j],DP[j-i]+i*alpha(F[i],i));
for(int i=1;i<=m;i++)DP[i]=max(DP[i],DP[i-1]);
printf("%d\n",DP[m]);
}
return 0;
}
TIPS
代码中的 \(F\) 数组只能是精确值,所以要附极小值
而 \(DP\) 无此要求
不理解的话看一下此数据
Imput:
1
1 1000
998 499Output:
499Wrong Answer:
500