在K上卡了很久,感觉很需要积累技巧
参考@Sicko的题解
参考@muschuang123的题解
K 枚举 贪心 策略 思维

最优大致思路是先用操作1把石像都转到同一个方向,然后操作2一起转到正面
当时卡在两个地方
- 怎么转到同一个方向
- 转到同一方向面向哪里
操作1的理解:
- 设每个方向的石像数量是 x i x_i xi,先后选中 i 、 i + 1 i、i+1 i、i+1方向的一个石像进行操作1

- 可见,原来 x i x_i xi集合中的石像可以慢慢转移到原来 x i − 1 x_{i-1} xi−1集合中,可以看作 i i i方向的石像可以逆向转到 i − 1 i-1 i−1方向,需要 b ( 原 x i ) b(原x_i) b(原xi)个操作1。把所有的转到同一方向,原 x i + 2 、 x i + 3 x_{i+2}、x_{i+3} xi+2、xi+3分别需要2、3倍的操作,相加可得出转到同一方向的操作数
- 一个石像转4下会转到原方向,所以操作2最多用3次,可以对重复的转圈取模
cpp
void solve(){
int n;cin>>n;
vector<int>a(n+1),x(5,0);
forr(i,1,n){
cin>>a[i];
x[a[i]]++;
}
vector<int>cnt(5,0);
forr(i,0,3){// 枚举每个方向找最小操作1数
forr(j,1,n){
cnt[i]=x[(i+1)%4]+x[(i+2)%4]*2+x[(i+3)%4]*3;// 转到同一方向所需步数
}
}
int ans=inf;
forr(i,0,3){
ans=min(ans,cnt[i]+(4-(cnt[i]+i)%4)%4);// 转到同一方向面向(i+cnt[i])%4 再转到0
}
cout<<ans<<endl;
}
另一种理解
按住一个石像转 1 次 + 按住自己转 3 次 = 把一个石像往反方向转 1 次
cpp
const int N=1e6+8;
int a[N],f[4];
signed main(){
int n;cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=0;i<4;i++){
for(int j=1;j<=n;j++){
f[i]+=(a[j]-i+4)%4;// 反向转次数
}
}
int minn=1e9;
for(int i=0;i<4;i++){
minn=min(minn,f[i]+(f[i]*3+4-i)%4);
}
cout<<minn<<"\n";
return 0;
}
D 思维 几何

题意:找一个垂直于坐标轴的平面,找最多能和多少长方体内给定直线交
因为是垂直于坐标轴,可以只看一维轴,平面就是轴上一个点,长方体内直线就是轴上一个区间,抽象为轴上一个点最多能在多少区间内
cpp
/*
从左向右枚举左端点l 之前放入的r'>l 那么就和之前的这条线相交
*/
int cal_insec(vector<pii>a){
sort(a.begin(),a.end());// 左端点从小到大
priority_queue<int,vector<int>,greater<int>>rq;
int mx=0;
for(auto [l,r]:a){
while (rq.size()&&rq.top()<l)rq.pop();
mx=max(mx,(int)rq.size()+1);
rq.push(r);
}
return mx;
}
void solve(){
int n,a,b,c;cin>>n>>a>>b>>c;
vector<pii>x,y,z;
forr(i,1,n){
int x1,y1,z1,x2,y2,z2;
cin>>x1>>y1>>z1>>x2>>y2>>z2;
if(x1>x2)swap(x1,x2);
if(y1>y2)swap(y1,y2);
if(z1>z2)swap(z1,z2);
// 三维转一维线段 找一维线段相交
x.push_back({x1,x2});
y.push_back({y1,y2});
z.push_back({z1,z2});
}
// cout<<cal_insec(x)<<endl<<cal_insec(y)<<endl<<cal_insec(z)<<endl;
cout<<max({cal_insec(x),cal_insec(y),cal_insec(z)})<<endl;
}
F 贪心 化式子

cpp
void solve(){
int n,k;
double P,L,R;
cin>>n>>k;
vector<double>c(n+1,0),r(n+1,0);
cin>>r[0]>>c[0]>>P>>L>>R;
forr(i,1,k){
int p;double v;
cin>>p>>v;
r[p]=v;
}
/*
c_i=p*c_{i-1}+(1-p)*r_{i-1}
两项系数统一 c_{i-1}-c_i=(1-p)*c_{i-1}-(1-p)*r_{i-1}=(1-p)*(c_{i-1}-r_{i-1})
c_i-r_i=(c_i-c_{i+1})/(1-p)
1~n求和得(c_1-c_{n+1})/(1-p) c_{n+1}越小越好 即不指定的r 越小越好
*/
double ans=0;
forr(i,1,n){
if(r[i]==0)r[i]=L;
c[i]=P*c[i-1]+(1-P)*r[i-1];
ans+=c[i]-r[i];
}
cout<<fixed<<setprecision(8)<<ans<<endl;
}
G dp

题意:至少有多少连续通道权值乘积>x
cpp
const int N = 2e5+10,M=1e5;
const double PI=acos(-1);
const long long mod =998244353, inf = 1e9+10 ;
// struct trans
// {
// int u,v,d;
// };
vector<pii>g[N];
int dp[N][35];// dp[i点出发][经过j条边]=最大权值乘积
void solve(){
int n,m,q;cin>>n>>m>>q;
forr(i,1,n)forr(j,0,31)dp[i][j]=1;
// forr(i,1,m){
// int u,v,d;cin>>u>>v>>d; 应该按点存边更新 每次遍历更新每个点
// forr(j,1,30){
// dp[u][j]=max(dp[u][j],dp[u][j-1]*d);
// }
// }
/*
发现d取值至少是2,x<=1e9,那么单次询问经过的边数一定不会多于30条
最多用30条路 把所有的路更新30次
*/
// vector<trans>a(n+1);
forr(i,1,m){
int u,v,d;cin>>u>>v>>d;
g[u].push_back({v,d});
}
forr(j,0,31){
forr(i,1,n){
for(auto [x,d]:g[i]){
dp[i][j+1]=max(dp[i][j+1],min(inf,dp[x][j]*d));// 从一个起点转移出边
}
}
}
forr(i,1,q){
int p,x;cin>>p>>x;
int pos=upper_bound(dp[p],dp[p]+32,x)-dp[p];
if(x/dp[p][pos]==0)cout<<pos<<endl;
// 另一种枚举找法
// forr(pos,1,30){
// if(x/dp[p][pos]==0){
// cout<<pos<<endl;
// break;
// }
// }
}
}
I 双指针 组合计数 容斥
k 是确定的,这提示我们使用双指针。
每个区间修改后1的数量不变, [ l , r ] [l,r] [l,r]区间贡献是 C r − l + 1 k C_{r-l+1}^k Cr−l+1k,中间会有和其他区间重复的贡献,就是和其他区间重叠的1数量不变的部分
cpp
const int N = 1e5,M=1e5;
const double PI=acos(-1);
const long long mod =998244353, inf = 1e9+10 ;
int fac[N+10],ifac[N+10];
int qpow(int a,int b,int p){
int res=1;
while (b)
{
if(b&1)(res*=a)%=p;
b>>=1;
(a*=a)%=p;
}
return res%p;
}
int inv(int x){
return qpow(x,mod-2,mod)%mod;
}
void init(){
fac[0]=1;
forr(i,1,N){
fac[i]=fac[i-1]*i%mod;
}
ifac[N]=inv(fac[N]);
reforr(i,0,N-1){
ifac[i]=ifac[i+1]*(i+1)%mod;
}
}
int C(int n,int m){
return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
void solve(){
int n,k;
string s;cin>>n>>k>>s;
s=' '+s;
/*
双指针 拓展最长的有k个1的段
任意取的最长段 1的数量都不变
*/
vector<int>pre(n+1,0);
forr(i,1,n){
pre[i]=pre[i-1]+(s[i]-'0');
}
if(pre[n]<k)return cout<<0<<endl,void();
int ans=0;
if(k==1){
vector<int>cnt;
int z=0;
forr(i,1,n){
if(s[i]=='1'){
cnt.push_back(z);
z=0;
if(cnt.size()>1){
int tp=cnt.size();
ans+=cnt[tp-1]+cnt[tp-2];
}
}else z++;
}
cnt.push_back(z);
int tp=cnt.size();
ans+=cnt[tp-1]+cnt[tp-2]+1;
}else{
int nowl=1,nowr=0,lstr=-1;
while (nowr<n)
{
while(nowr<n&&pre[nowr+1]-pre[nowl-1]<=k)nowr++;
if(pre[nowr]-pre[nowl-1]<k)break;// 判断区间1的个数
int lap=0;// 和上一段重叠部分
if(lstr>=nowl)lap=C(lstr-nowl+1,pre[lstr]-pre[nowl-1]);// nowl~lstr这一段的1数量不变 就是两段都会算的部分
(ans+=C(nowr-nowl+1,pre[nowr]-pre[nowl-1])-lap+mod)%=mod;
while(nowl<=nowr&&pre[nowr]-pre[nowl-1]>=k)nowl++;// 摆脱1数量=k的这一段
lstr=nowr;
}
}
cout<<ans<<endl;
}