OI 笑传 #25

感觉落下了好多东西要写,先写写 ABC431。被 E 吓跑了写了 F。

ABC431D

今年 T1 既视感。

首先贪心把幸福感更高的放进头和身子,这样一定最优但是不一定合法。

然后考虑从头里选出一些扔进身子,选的重量最少是重量差除二上取整。把价值设为头和身子的幸福差值,01 背包即可。

code

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
struct work{
  i64 w;
  int h1;
  int b1;
}p[10000];
vector<work> h,b;
int w[10000];
i64 cst[10000];
i64 dp[550*550];
int main(){
  
  int n;cin>>n;
  i64 sumhw=0,sumbw=0,eh=0,eb=0;
  for(int i=1;i<=n;i++){
    cin>>p[i].w>>p[i].h1>>p[i].b1;
    if(p[i].h1>p[i].b1){
      h.push_back(p[i]);
      sumhw+=p[i].w;
      eh+=p[i].h1;
    }
    else b.push_back(p[i]),sumbw+=p[i].w,eb+=p[i].b1;
  }
  if(sumhw<=sumbw){
    cout<<eh+eb;
    return 0;
  }
  int cnt=0;
  int del=(sumhw-sumbw+1)/2+550;
  for(work ele:h){
    cnt++;
    w[cnt]=ele.w;
    cst[cnt]=ele.b1-ele.h1;
  }
  for(int i=0;i<=del+599;i++)dp[i]=-999999999999999;
  dp[0]=0;
  for(int i=1;i<=cnt;i++){
    for(int j=del;j>=w[i];j--){
      dp[j]=max(dp[j],dp[j-w[i]]+cst[i]);
    }
  }
  i64 mina=-999999999999999;
  for(int i=del-550;i<=del;i++){
    mina=max(mina,dp[i]);
  }
  cout<<eh+eb+mina;
  
  return 0;
}

ABC431E

想到了建图然后就否了,结果确实要建图而且让我否的理由其实无影响,哎这不就是我 T2 吗。

考虑给网格图每个网格边放个点然后变成图论题。然后根据每个网格初始的镜子状态连上 \(0\) 权边,要改变才行的给连上 \(1\) 权边。

然后实际上跑 Dij 还是 01BFS 就是对的了。关键是一个点。

我们不能跟着光走变镜子状态,那我们把对向的边变成 \(1\),如果光线要走两遍这个格子不是会多算一遍吗?

其实不会的,你想想走两遍这个格子是要干什么,无非就是转向或者直走,那样的话直接走对应的 0 权边就行了,没有影响的。

于是这个东西就是对的了。

还没有代码。

ABC431F

经典 DP,原题是:P6522,算 DP 吗?

这种前后有限制的东西都要想想从小到大或者从大到小加数。我们在这里选从小到大,因为这样一个好处是不管插哪里前一个数一定比自己小就一定是合法的,考虑后面的数即可。

然后维护现在已经放进序列的数的集合,显然我们能塞到它前面的数就是比自己减去 \(D\) 还大于等于的这些数。

我们只关心后面的数,因此位置不重要,方案数直接乘起来即可。

但这样我们还考虑了每一个数都是不一样的,原题认为一样于是我们给每种数除上它出现次数的阶乘即可,消去顺序。这里用逆元。

维护集合用的桶和 BIT。

以及注意模数并非 \(1000000007\),我为什么被这个卡了一会。

code

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int mod=998244353;
const int N=3e5+66;
const int LM=1e6+66;
int a[N];
int c[LM+1116];
int b[LM+1116];
int jc[LM+1116];
int ksm(int a,int b){
	i64 res=1,k=a;
	while(b){
		if(b&1)res=res*k%mod;
		k=k*k%mod;
		b>>=1;
	}
	return res;
}
void add(int p){
  for(;p<=LM;p+=((-p)&p)){c[p]+=1;}
  return ;
}
int qry(int p){
  int res=0;
  for(;p>0;p-=((-p)&p))res+=c[p];
  return res;
}
int main(){
  
  int n,d;cin>>n>>d;
  jc[0]=1;
  for(int i=1;i<=n;i++){
    cin>>a[i];b[a[i]]++;
    jc[i]=1ll*jc[i-1]*i%mod;
  }
  sort(a+1,a+1+n);
  i64 ans=1;
  add(a[1]);
  for(int i=2;i<=n;i++){
    int pr=a[i]-d-1;
    pr=max(0,pr);
    int un=qry(pr);
    int ph=((i-1)-un+1);
    ans=ans*ph%mod;
    add(a[i]);
  }
  // cout<<ans;
  for(int i=1;i<LM;i++){
  	if(b[i]!=0){
  		ans=ans*ksm(jc[b[i]],mod-2)%mod;
		}
	}
	cout<<ans;
  
  return 0;
}
posted @ 2025-11-09 23:37  hm2ns  阅读(7)  评论(0)    收藏  举报