CF960F Pathwalks
正解:线段树优化dp,转移很简单,类似于LIS
解法二:开一个map,然后 m a p i , j map_{i,j} mapi,j代表通过编号为 j j j这一条边,到第 i i i个点时的最大长度
m a p v , j = m a x ( m a p u , k ) map_{v,j}=max(map_{u,k} ) mapv,j=max(mapu,k) —> k用map自带的迭代器枚举( k < j k
那么我们考虑二分查找,但二分要求随 k k k的增大, m a p u , k map_{u,k} mapu,k也增大,所以我们可以用类似单调队列的思想,如果我们每次更新后 m a p v , j map_{v,j} mapv,j,存在 m a p v , l < m a p v , j map_{v,l}
#include
#define ll long long
using namespace std;
map<int,int> dp[100005];//维护的map第二关键字随第一关键字的增大而增大
int find(int u,int w){
auto it=dp[u].lower_bound(w);
return it==dp[u].begin()?0:(--it)->second;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,m;
cin>>n>>m;
int ans=0;
for (int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
int maxn=find(u,w)+1;//找到到达u点,最后一段路价值小于w的最长距离+1
if (maxn>find(v,w)){
auto it=dp[v].lower_bound(w);
dp[v][w]=maxn;
while (it!=dp[v].end()&&it->second<maxn){//保证map第二关键字单调
dp[v].erase(it++);//it迭代删除后面w大但距离小的点
}
ans=max(ans,maxn);
}
}
cout<<ans<<"\n";
return 0;
}
CF417D Cunning Gena
状压dp , d p i , j dp_{i,j} dpi,j表示前 i i i个人中,已经能做出的题目状态为 j j j的最小花费(不包括显示屏)
预处理每个人能做的题目的状态 用或|运算转移即可
可以用滚动数组优化掉一维空间
每次枚举一个 i i i更新一下当前做出所有题目的最小花费 a n s ans ans
#include
#define ll long long
using namespace std;
struct node{
ll cost,val,tol;
};
bool cmp(node x,node y){
return x.tol<y.tol;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,m,b;
cin>>n>>m>>b;
vector<node> a(n+1);
for (int i=1;i<=n;i++){
int x;
cin>>a[i].cost>>a[i].tol>>x;
int temp=0;
while (x--){
int y;
cin>>y;
temp|=(1<<(y-1));
}
a[i].val=temp;
}
sort(a.begin(),a.end(),cmp);
vector<vector<ll>> dp(2,vector<ll>(1<<m,2e18+5));
int now=0;
dp[0][0]=0;
ll ans=2e18+5;
for (int i=1;i<=n;i++){
for (int j=0;j<(1<<m);j++){
dp[now^1][j|(a[i].val)]=min(dp[now^1][j|(a[i].val)],dp[now][j]+a[i].cost);
dp[now^1][j]=min(dp[now^1][j],dp[now][j]);
if ((j|a[i].val)==(1<<m)-1){
ans=min(ans,a[i].tol*b+dp[now^1][(1<<m)-1]);
}
dp[now][j]=2e18+5;
}
now^=1;
}
if (ans==2e18+5){
cout<<"-1";
}else{
cout<<ans<<"\n";
}
return 0;
}
CF893E Counting Arrays
排列组合,考虑 n n n中每个质因子的贡献
比如 n n n 中质因子 m m m 有 s u m sum sum个质因子,那么这 s u m sum sum个质因子可以任意分配到 y y y 段中,用隔板法进行统计,组合的计算求逆元
阶乘和阶乘的逆元预处理一下即可
#include
#define ll long long
using namespace std;
const ll mod=1e9+7;
ll fac1[1000025],fac2[1000025];
ll ksm(ll a,ll x){
ll s=a,sum=1;
while (x){
if (x&1){
sum=(sum*s)%mod;
}
s=(s*s)%mod;
x>>=1;
}
return sum;
}
ll ni(ll x){
return ksm(x,mod-2);
}
void solve(){
int x,y;
cin>>x>>y;
vector<int> sum;
for (int i=2;i*i<=x;i++){
if (x%i==0){
int tol=0;
while (x%i==0){
x/=i;
tol++;
}
sum.push_back(tol);
}
}
if (x!=1){
sum.push_back(1);
}
ll ans=ksm(2,y-1);
for (auto i:sum){
// cout<
ans=ans*(fac1[i+y-1]*fac2[y-1]%mod*fac2[i]%mod)%mod;
}
cout<<ans<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
fac1[0]=fac1[1]=1;
for (int i=2;i<=1000020;i++){
fac1[i]=(fac1[i-1]*i)%mod;
}
fac2[0]=fac2[1]=ni(1);
for (int i=2;i<=1000020;i++){
fac2[i]=(fac2[i-1]*ni(i))%mod;
}
int t;
cin>>t;
while (t--){
solve();
}
return 0;
}
CF167B Wizards and Huge Prize
概率dp的简单题,每一步决策很简单就是输或赢
d p i , j , k dp_{i,j,k} dpi,j,k表示枚举到第 i i i件物品,赢了 j j j次,背包空间剩下 k k k体积时的概率 注意一下边界范围 初始化就好
然后枚举时可能会出现物品对空间价值为-1,导致背包空间暂时为负数的情况,所以可以先选(更新)价值为正的物品,排序即可
#include
#define ll long long
using namespace std;
struct node{
int p,val;
};
double dp[205][205][205];
bool cmp(node x,node y){
return x.val>y.val;
}
int main(){
int n,l,v;
cin>>n>>l>>v;
vector<node> a(n+1);
for (int i=1;i<=n;i++){
scanf("%d",&a[i].p);
}
for (int i=1;i<=n;i++){
scanf("%d",&a[i].val);
}
sort(a.begin()+1,a.end(),cmp);
dp[0][0][v]=1;
for (int i=1;i<=n;i++){
for (int j=0;j<=n;j++){
for (int k=0;k<=200;k++){
if (j<n){
if (k+a[i].val>200){
if (k+a[i].val>=0)
dp[i][j+1][200]+=dp[i-1][j][k]*a[i].p*0.01;
}else {
if (k+a[i].val>=0)
dp[i][j+1][k+a[i].val]+=dp[i-1][j][k]*a[i].p*0.01;
}
}
dp[i][j][k]+=dp[i-1][j][k]*(100-a[i].p)*0.01;
}
}
}
double ans=0;
for (int j=l;j<=n;j++){
for (int k=0;k<=200;k++){
ans+=dp[n][j][k];
}
}
printf("%.12lf",ans);
return 0;
}
P2057 [SHOI2007] 善意的投票 / [JLOI2010] 冠军调查
一道经典的最小割模型 ,详细证明看题解
先分阵营连边,再两两朋友连双向边,跑最大流即可
int main(){
cin>>n>>m;
s=n+1;t=n+2;
for (int i=1;i<=n;i++){
int x;
cin>>x;
if (x)add(s,i,1);
else add(i,t,1);
}
for (int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
add(x,y,1);
add(y,x,1);
}
cout<<dinic();
return 0;
}
一道图上LIS(CF960F Pathwalks)主席树优化方法待补;
一道状压dpCF417D Cunning Gena;
一道概率dpCF167B Wizards and Huge Prize;
一道排列组合CF893E Counting Arrays;
一道最小割的经典模型P2057 [SHOI2007] 善意的投票 / [JLOI2010] 冠军调查
今天dp的种类很多,状态设计和转移都比较简单,但是一些细节和处理比较巧妙,例如概率dp的先排序再dp,边界范围出了几次问题;写状压dp的时候漏掉了当前点不选的转移;补了一下排列组合的插板法公式;复习了一点基础的线段树;了解了一下最小割的经典模型
明日计划:尽可能多做几道dp,下午5点打一场cf,补cf的题,然后继续做dp,有时间可以把网络流最小割的变式题做了,或者继续学习线段树