D 贪心 排序 二分查找

一开始没仔细想去做三分了...
题意:设置一个难度x,每把刀可以砍一次,一关砍 b i b_i bi次,所有威力 a i ≥ x a_i\geq x ai≥x的剑都可以拿去砍,最后得分 = 闯关数 × x =闯关数\times x =闯关数×x
x越大,可用的剑变少,能创的关数可能变少
所以x应该在闯关数一样的情况下尽量大
x最优是选到每把剑的威力,从小到大枚举x,用前缀和二分查找闯关数
cpp
void solve(){
int n;cin>>n;
vector<int>a(n+1),b(n+2);
b[n+1]=1e9+1;//为了能找到 闯关数=n 的情况
forr(i,1,n){
cin>>a[i];
}
forr(i,1,n)cin>>b[i];
vector<int>pb(n+1,0);
forr(i,1,n){
pb[i]=pb[i-1]+b[i];
}
sort(a.begin()+1,a.begin()+n+1);
int ans=0;
forr(i,1,n){
if(a[i]==a[i-1])continue;//已经被枚举过的威力值就跳过
int p=upper_bound(pb.begin()+1,pb.end(),n-i+1)-pb.begin();//找第一个>可用剑数的关 这个关前面的都闯过去了
p--;
ans=max(ans,p*a[i]);
}
cout<<ans<<endl;
// auto cal=[&](int x)->int{
// int cnt=0;
// forr(i,1,n){
// if(a[i]>=x)cnt++;
// }
// int mark=-1;
// forr(i,1,n+1){
// if(b[i]>cnt){
// mark=i-1;
// break;
// }else cnt-=b[i];
// }
// return mark*x;
// };
// int l=0,r=amx+1;
// int ans=0;
// while (l<r)
// {
// int lm=l+(r-l)/3,rm=r-(r-l)/3;
// int lc=cal(lm),rc=cal(rm);
// // cout<<lm<<' '<<lc<<' '<<rm<<' '<<rc<<endl;
// if(lc<rc)l=lm+1;
// else if(lc==rc){
// if(lc==0)r=rm-1;
// else l=lm+1;
// }
// else r=rm-1;
// // int mid=(l+r)/2;
// // int lc=cal(mid-1),rc=cal(mid+1);
// // if(lc<=rc)l=mid;
// // else r=mid;
// }
// ans=max({ans,cal(l),cal(r)});
// // cout<<"ans";
// cout<<ans<<endl;
}
E dp(有点像完全背包?)

题意:求 1 ∼ n 1\sim n 1∼n每个数能从 a 1 ∼ a n a_1\sim a_n a1∼an选最少多少个数相乘得到
d p [ i × j ] = m i n ( d p [ i ] + d p [ j ] , d p [ i × j ] ) dp[i\times j]=min(dp[i]+dp[j],dp[i\times j]) dp[i×j]=min(dp[i]+dp[j],dp[i×j]), i , j i,j i,j都可以用数组中的数相乘得到,简单来想,可以把 i i i规定为 a a a数组中的数,j枚举 2 ∼ n 2\sim n 2∼n每个数( i × j ≤ n i\times j\leq n i×j≤n)
cpp
void solve(){
int n;cin>>n;
set<int>a;
forr(i,1,n){
int x;cin>>x;
a.insert(x);
}
vector<int>dp(n+1,inf);
for(auto x:a)dp[x]=1;//初始化
for(auto x:a){//枚举a中数(可选物品)
for(int i=2;i*x<=n;i++){//枚举2~n(已有物品基础上)
dp[x*i]=min(dp[x*i],dp[x]+dp[i]);
}
}
forr(i,1,n){
cout<<(dp[i]==inf?-1:dp[i])<<' ';
}cout<<endl;
}
F 线性dp

题意:横轴x轴,每次只能向上下右走,x不能往回走,从 ( a x , a y ) → ( b x , b y ) (a_x,a_y)\rightarrow (b_x,b_y) (ax,ay)→(bx,by)遍历每个点求最短路径
x的单向性可以想到层层递推,每层要遍历每个点,就是要走遍每层最左最右的点
有点像这个题 P3842 [TJOI2007] 线段

cpp
//dp[i][0]表示到第i行左端点最小代价 dp[i][1]表示到右端点 len是每行线段长度
dp[1][0]=r[1]-1+len[1],dp[1][1]=r[1]-1;
forr(i,2,n){
dp[i][0]=min(dp[i-1][0]+abs(r[i]-l[i-1])+len[i],dp[i-1][1]+abs(r[i-1]-r[i])+len[i]);
dp[i][1]=min(dp[i-1][0]+abs(l[i]-l[i-1])+len[i],dp[i-1][1]+abs(r[i-1]-l[i])+len[i]);
}
int ans=(n-1)+min(dp[n][0]+n-l[n],dp[n][1]+n-r[n]);
状态转移方程推导如图
dp[i][1]同理
F题代码:
cpp
void solve(){
int n,ax,ay,bx,by;
cin>>n>>ax>>ay>>bx>>by;
vector<pii>pos(n+1);
forr(i,1,n)cin>>pos[i].first;
forr(i,1,n)cin>>pos[i].second;
sort(pos.begin()+1,pos.end());
vector<int>x;vector<vint>y;
x.push_back(ax);y.push_back({ay});
for(int i=1;i<=n;i++){
x.push_back(pos[i].first);
int si=i;
vector<int>yt;
while(i<=n&&pos[si].first==pos[i].first){
yt.push_back(pos[i].second);
i++;
}
y.push_back(yt);
i--;
}
x.push_back(bx);y.push_back({by});
int m=x.size();
vector<array<int,2>>dp(m+1,{inf,inf});
dp[0][0]=dp[0][1]=0;
forr(i,1,m-1){
int l1=y[i-1].front(),r1=y[i-1].back(),l2=y[i].front(),r2=y[i].back();
int need=(x[i]-x[i-1])+(r2-l2);//走到第i行+走遍第i行 必要的步数
dp[i][0]=min(dp[i-1][0]+abs(r1-l2),dp[i-1][1]+abs(l1-l2))+need;
dp[i][1]=min(dp[i-1][0]+abs(r1-r2),dp[i-1][1]+abs(l1-r2))+need;
}
cout<<dp[m-1][0]<<endl;
}
G DFS DFN序 思维

题意:图是一棵树(无环、连通、无向),所以任意两点 x,y 之间的路径是唯一的。
每次查询一对 (a,b),在不超过一半查询内,找到一个在 x 到 y 路径上的点。
次数不超过一半,所以每次问的a,b两点要覆盖整个树,并且不要重复
询问的回答:
- 回答 0:两条路径完全不相交。(这个路径上所有点都不可能成为结果)
- 回答 1:x 到 y 的路径与 a 到 b 的路径有公共顶点。
这个路径某个点可能成为结果
继续在这个路径上怎么找才能控制住查询次数?
如果本次询问的 ( a ′ , b ′ ) (a',b') (a′,b′)路径上其他点都被问过并排除过了,只问 a ′ , b ′ a',b' a′,b′两点就可以了
所以之前询问的 ( a , b ) (a,b) (a,b)要把 ( a ′ , b ′ ) (a',b') (a′,b′)路径上其他点都覆盖并排除,
dfn它能把树的结构映射成线性序列,dfn序相邻的点之间的路径没有没有处理过的点。
定义:在DFS遍历过程中,首次访问节点时的编号序列(DFN即"Depth-First Number"的缩写)。每个节点的DFN值是其被首次发现的时间戳,仅记录1次。
一文看懂DFS序、DFN序与欧拉序
cpp
int ask(int u,int v){
cout<<"? "<<u<<' '<<v<<endl;
fls;
int res;cin>>res;return res;
}
void ans(int x){
cout<<"! "<<x<<endl;
fls;
}
void solve(){
int n;cin>>n;
vector<vint>g(n+1);
forr(i,1,n-1){
int u,v;cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
//找dfn序
vector<int>dfn;
auto dfs=[&](auto&& dfs,int now,int fa)->void{
dfn.push_back(now);
for(auto x:g[now]){
if(x==fa)continue;
dfs(dfs,x,now);
}
};
dfn.push_back(0);
dfs(dfs,1,0);
//问相邻的点
for(int i=1;i<n;i+=2){
if(ask(dfn[i],dfn[i+1])){
if(ask(dfn[i],dfn[i]))ans(dfn[i]);
else ans(dfn[i+1]);
return;
}
}
if(n&1)ans(dfn[n]);
return;
}
G DFS 拓扑排序(构造入度) 奇偶性

写力竭了...总之删的大致思路是 偶权值点(不会改变权值和奇偶性)->奇权值点和之前没被删掉的偶权值点
cpp
//奇偶性、加减->异或
//异或1:反转 异或0:不变 偶权值点对权值和奇偶性无影响
//思路:先去掉能去掉的偶权值点 再着力去掉几个能去掉的奇权值点
//由于只有奇权值点才会对消除有影响 只建立奇权值点的树
//发现可以去掉的奇权值点必连了偶数条边 消除它就会消除偶数条边
//如果一个树中有奇数条边 消到最后剩一条边连两个奇权值点 消不掉
void solve(){
int n;cin>>n;
vector<int>a(n+1),s(n+1,0),vis(n+1,0),ans;
forr(i,1,n){
cin>>a[i];
a[i]&=1;
}
vector<vint>g(n+1);
forr(i,1,n-1){
int u,v;cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
s[u]^=a[v];
s[v]^=a[u];
}
//先去掉权重为偶数的点
forr(i,1,n)if(!a[i]&&s[i]){
ans.push_back(i);
vis[i]=1;
}
//建奇数权重树/森林
vector<vint>odd_g(n+1);
auto dfs=[&](auto &&dfs,int now,int fa)->void{
for(auto x:g[now]){
if(x==fa)continue;
if(a[now]&&a[x]){
odd_g[now].push_back(x);
odd_g[x].push_back(now);
}
dfs(dfs,x,now);
}
return;
};
dfs(dfs,1,0);
//遍历每个奇数权重树 并用拓扑排序消除
vector<int>deg(n+1,0),sz(n+1),tree; //入度/大小/树中的点
auto odd_dfs=[&](auto&& odd_dfs,int now,int fa)->void{
sz[now]=1;tree.push_back(now);
for(auto x:odd_g[now]){
if(x==fa)continue;
odd_dfs(odd_dfs,x,now);
//构造deg 要消除一个点 必须消除偶数个边
if(sz[x]%2)deg[x]++; //如果x为根的子树大小为奇数 x<-now
// 子树中边数为偶数 能消完所有的边 最后剩x一个点 只能等消除now来消除x-now的边 再消除x
else deg[now]++; //如果x为根的子树大小为偶数 x->now
// 子树中边数为奇数 消不完所有的边 会剩一条和x相连 x的权值和为偶数 x可以直接被消除
sz[now]+=sz[x];
}
return;
};
auto topo=[&]()->void{
queue<int>q;
for(auto x:tree)if(deg[x]==0)q.push(x);
while (q.size())
{
int now=q.front();q.pop();
ans.push_back(now);vis[now]=1;
for(auto x:g[now]){
if(!vis[x]){
if(a[x]==0){
ans.push_back(x);//x是偶数 之前消不掉 消掉和它相邻的奇数后可以消掉
vis[x]=1;
}
else{
if(--deg[x]==0)q.push(x);
}
}
}
}
return;
};
forr(i,1,n){
if(!vis[i]&&a[i]){
//每个奇数权重树
tree.clear();
odd_dfs(odd_dfs,i,0); //先遍历统计
if(sz[i]%2==0){ //偶数个点奇数个边 这棵树删不完
return rno,void();
}
topo();
}
}
forr(i,1,n)if(!vis[i])return rno,void();
ryes;
for(auto x:ans)cout<<x<<' ';cout<<endl;
}