主播菜死了,只拿了120分
T1
注:
1
≤
t
1
≤
1
0
9
1\le t_1\le10^9
1≤t1≤109。
考虑从空开始加边。这样做是等效的。所以肯定先尽量使得 max { T 1 , T 2 } \max\{T_1,T_2\} max{T1,T2} 最小(其中 T 1 , T 2 T_1,T_2 T1,T2 都是已经形成的子树。现在要代表元上连边)。可以发现刚开始的合并事实上每个点都会算一次,之后子树间的合并最大值还会分别再算一次。如果没有算上,则显然会有遗漏。如果算多了,应该先合并合适的子树。发现在最优的情况下,我们应该最后合并最大的 t i t_i ti 的点。故答案为 ∑ t + ∑ ( u , v ) ∈ E max { t u , t v } − max { t i } \sum t+\sum\limits_{(u,v)\in E}\max\{t_u,t_v\}-\max\{t_i\} ∑t+(u,v)∈E∑max{tu,tv}−max{ti} 。
T2
注:一个人的排名是学习能力大于他的的人数。
对于每个学生,首先考虑若不给该学生书,怎样安排排名不变。一个显而易见的结论:若一个人看书超了另一个人,除非他也看书,否则没法超过,排名下降。所以只有在学习能力 ≤ ⌊ x i t ⌋ , ≥ x i + 1 \le\left\lfloor\frac{x_i}{t}\right\rfloor,\ge x_i+1 ≤⌊txi⌋,≥xi+1 的人,可以看书。用组合数算即可。
考虑第二种情形: x i → t × x i x_i\to t\times x_i xi→t×xi,则所有 [ x i + 1 , t × x i ] [x_i+1,t\times x_i] [xi+1,t×xi] 学生都必须看书。(考虑清楚边界)。在 [ t × x i + 1 , m x ] , [ 1 , x i ] [t\times x_i+1,mx],[1,x_i] [t×xi+1,mx],[1,xi] 学生都可以看书。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+1,P=1e9+7;
struct {
int fac[N];
int ifac[N];
int inv[N];
int ans[N];
int binom(int n,int m){
if(n<m||m<0) return 0;
return fac[n]*ifac[m]%P*ifac[n-m]%P;
}
void init(int n){
fac[0]=ifac[0]=inv[0]=1;
fac[1]=ifac[1]=inv[1]=1;
for(int i=2;i<=n;++i){
fac[i]=fac[i-1]*i%P;
inv[i]=(P-P/i)*inv[P%i]%P;
ifac[i]=ifac[i-1]*inv[i]%P;
}
//printf("C(3,2)=%lld\n",binom(3,2));
}
struct {
int id,x;
} a[N];
void LOL(){
//freopen("rank2.in","r",stdin);
//freopen("rank2.o","w",stdout);
int n,k,t;
cin>>n>>k>>t;
init(n);
for(int i=1;i<=n;++i)
cin>>a[i].x,a[i].id=i;
sort(a+1,a+n+1,[](auto a,auto b){
return a.x<b.x;
});
auto Find=[&](int x){ //最大的小于等于x的数的位置
int l=1,r=n,mid;
while(l<=r){
mid=l+r>>1;
if(a[mid].x<=x) l=mid+1;
else r=mid-1;
}
return l-1;
};
auto Find1=[&](int x){ //最小的大于等于x的数的位置
int l=1,r=n,mid;
while(l<=r){
mid=l+r>>1;
if(a[mid].x>=x) r=mid-1;
else l=mid+1;
}
return r+1;
};
for(int i=1;i<=n;++i){
int c1=Find(a[i].x/t);//小于等于 x[i]/t 的,不会影响大于x[i]的数的数量
int c2=n-Find(a[i].x);
//printf("考虑 %lld 前面有 %lld 个数可选,后面有 %lld 个数可选\n",a[i].id,c1,c2);
int ans1=binom(c1+c2,k);
//printf("在不选当前数的情形下,有 %lld 种方案\n",ans1);
int c3=Find(a[i].x*t);//等于 a[i].x*t 的,也要乘
//printf("若考虑当前数,则 [x[i]+1,t*x[i]] 都要选\n");
int c4=Find1(a[i].x+1); //+1///
//printf("x[i]+1 的位置是 %lld, a[i].x*t 的位置是 %lld\n",c4,c3);
int ch=k-(c3-c4+1)-1;
//printf("中间所以数都得选上。还有 %lld 没选的\n",ch);
int c5=Find(a[i].x);
//printf("小于等于 x[i] 的或者大于 t*x[i],都可以乘。\n");
//printf("分别有 %lld %lld 个。\n",c5,n-c3);
int ans2=binom(n-c3+c5-1,ch);
ans[a[i].id]=(ans1+ans2)%P;
//printf("ans[%lld]=%lld\n",a[i].id,ans1+ans2);
}
for(int i=1;i<=n;++i)
cout<<ans[i]<<'\n';
}
} DNF;
main() {
DNF.LOL();
}
T3
记:
a
a
a 为串
S
S
S,
b
b
b 为串
t
t
t。
n
n
n 为
a
a
a 长度,
m
m
m 为
b
b
b 长度。
观察到只需要删除操作。增加其实是无用的。又发现将两个串修改相等其实就是求 LCS \operatorname{LCS} LCS。最终答案就是 L e n 1 + L e n 2 − 2 ∗ LCS Len_1+Len_2-2*\operatorname{LCS} Len1+Len2−2∗LCS。
维护每个字符在每个位置之后第一次出现的位置。设 n e i , c ne_{i,c} nei,c 表示位置 i i i 后,字符 c c c 首次出现的位置。用 O ( 26 n ) O(26n) O(26n) 的时间预处理出串 a a a 的 n e ne ne。
之后开一个大小 [ 22 ] [ 22 ] [22][22] [22][22] 的 d p dp dp 数组。 d p i , j dp_{i,j} dpi,j 表示考虑到 b b b 的第 i i i 个字符,使得 LCS ( a , b ) = j \operatorname{LCS}(a,b)=j LCS(a,b)=j 的最小的 a a a 的右端点。 ∀ i ∈ [ 0 , m ] , d p i , 0 = l − 1 , d p i , ∗ ≠ 0 = inf \forall i\in[0,m],dp_{i,0}=l-1,dp_{i,*\ne0}=\inf ∀i∈[0,m],dpi,0=l−1,dpi,∗=0=inf。 ∀ i ∈ [ 1 , m ] , ∀ j ∈ [ 1 , i ] , d p i , j = min { d p i − 1 , j , n e d p i − 1 , j − 1 + 1 , b i − ′ a ′ } \forall i\in[1,m],\forall j\in[1,i],dp_{i,j}=\min\{dp_{i-1,j},ne_{dp_{i-1,j-1}+1,b_i-'a'}\} ∀i∈[1,m],∀j∈[1,i],dpi,j=min{dpi−1,j,nedpi−1,j−1+1,bi−′a′}。含义是要不就单纯考虑前 i − 1 i-1 i−1 位,要不就加入最后一位,然后要匹配到对应的点,也就是他下一位匹配到 b i b_i bi。那么 a n s = max { d p i , j ∣ d p i , j ≤ r } ans=\max\{dp_{i,j}|dp_{i,j}\le r\} ans=max{dpi,j∣dpi,j≤r}。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m;
char a[N],b[1000];
int dp[300][300];
int ne[N][260];
main(){
cin.tie(0)->sync_with_stdio(0);
cin>>(a+1)>>(b+1);
n=strlen(a+1);
m=strlen(b+1);
for(int i=25;~i;--i)
ne[n+1][i]=ne[n+2][i]=n+1;
for(int i=n;i;--i) {
copy(ne[i+1],ne[i+1]+26,ne[i]);
ne[i][a[i]-'a']=i;
}
int q; cin>>q;
for(int L,R;q--;) {
cin>>L>>R;
//memset(dp,0x3f,sizeof dp);
for(int i=0;i<=m;++i){
fill(dp[i]+1,dp[i]+m+1,1e9);
dp[i][0]=L-1;
}
int LCS=0;
for(int i=1;i<=m;++i)
for(int j=1;j<=i;++j){
dp[i][j]=min(dp[i-1][j],ne[dp[i-1][j-1]+1][b[i]-'a']); //add b[i] or not
dp[i][j]<=R?LCS=max(LCS,j):0; //update max LCS's ending pos, ends with b[i] (with len of j)
}
cout<<R-L+1+m-2*LCS<<'\n';
}
}
T4
题解:
其实我也看不懂。大体就是离线扫描一遍,配合 n log n n\log n nlogn 的并查集(可撤销并查集)搞。(有大佬看懂了吗qwq
#include<bits/stdc++.h>
using namespace std;
const int N=(5e5+1)*2;
#define int long long
#define pb push_back
#define fi first
#define se second
using pii=pair<int,int>;
struct {
int n,m,t,fa[N],cnt;
int ans[N],s[N],sz[N],h[N];
struct{int op,x,y,id;}q[N];
struct ask{int op,x,id;};
vector<ask> qq[N];
pii g[N];
#define RMX(p,q) (p)<(q)?p=q,0:0;
int find(int x){
if(fa[x]==x) return x;
int ret=find(fa[x]);
RMX(g[x],g[fa[x]]);
return fa[x]=ret;
}
int find2(int x){
if(fa[x]==x) return x;
return find(fa[x]);
}
void merge(int x,int y){
x=find2(x),y=find2(y);
if(x==y) return ;
if(sz[y]>sz[x]) swap(x,y);
fa[y]=x,h[y]=s[x],sz[x]+=sz[y];
}
int query(int x){
int ans=0;
while(x^fa[x]) ans+=s[x]-h[x],x=fa[x];
return ans+s[x]-h[x];
}
void LOL(){
cin.tie(0)->sync_with_stdio(0);
cin>>n>>m,iota(fa,fa+n+1,0),cnt=n;
for(int i=1;i<=m;++i) {
auto&[op,x,y,id]=q[i];
cin>>op>>x,id=i;
if(op<5) cin>>y;
else {
find(x);
qq[g[x].fi].pb({-1,x,i});
qq[i].pb({1,x,i});
ans[i]+=g[x].se;
}
if(op==2)
fa[find(y)]=fa[find(x)]=++cnt,fa[cnt]=cnt;
if(op==4) g[find(x)]={i,y};//建立虚点
}
iota(fa,fa+cnt+1,0);
for(int i=1;i<=m;++i){
auto&[op,x,y,id]=q[i];
if(op==1) merge(x,y);
if(op==3) s[find2(x)]+=y;
for(auto[op,x,id]:qq[i])
ans[id]+=op*query(x);
}
for(int i=1;i<=m;++i)
if(q[i].op==5) cout<<ans[i]<<'\n';
}
} DNF;
main(){DNF.LOL();}