「NOWCODER」CSP-S模拟赛第3场
「NOWCODER」CSP模拟赛第3场
感觉是比较好的一套题 因为几乎都有思路
可惜只打了一个小时
附一下出题人的玄学题解
T1 货物收集
题目
考场思路即正解
一道比较简单的题,结果 因为编译器原因爆 了 咕咕咕
我的时间复杂度 。
就是处理出到达每个点所需要的最小武力值,再按照其从小到大排序。
最后用一个 lower_bound()
找到只需要到哪个点就行了。
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
#define rep(q,__a,__b) for(int q=__a,q##_end_=__b;q<=q##_end_;++q)
#define dep(q,__a,__b) for(int q=__a,q##_end_=__b;q>=q##_end_;--q)
template<class T>inline void qread(T& x){
char c;bool f=false;x=0;
while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int rqread(){
char c;bool f=false;int x=0;
while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return f?-x:x;
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline void getInv(int inv[],const int r,const int MOD)
{inv[0]=inv[1]=1;for(int i=2;i<=r;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;}
const int MAXN=1e6;
struct edge{
int to,nxt,w;
edge(){}
edge(const int T,const int N,const int W):to(T),nxt(N),w(W){}
}e[(MAXN<<1)+5];
int tail[MAXN+5],cnt;
inline void add_edge(const int u,const int v,const int w){
e[++cnt]=edge(v,tail[u],w);tail[u]=cnt;
e[++cnt]=edge(u,tail[v],w);tail[v]=cnt;
}
struct node{int v,low;
bool operator<(const node a)const{return low<a.low;}
}p[MAXN+5];
int N,W,pre[MAXN+5];
void dfs(const int u,const int pre,const int w){
p[u].low=w;
for(int i=tail[u],v;i;i=e[i].nxt)if((v=e[i].to)!=pre)
dfs(v,u,Max(w,e[i].w));
}
signed main(){
// freopen("2.in","r",stdin);
qread(N,W);
for(int i=2;i<=N;++i)qread(p[i].v);
for(int i=1,u,v,w;i<N;++i){
qread(u,v,w);
add_edge(u,v,w);
}
dfs(1,0,0);
sort(p+1,p+N+1);
rep(i,1,N)pre[i]=pre[i-1]+p[i].v;
// rep(i,1,N)printf("%d ",pre[i]);
int ansp=lower_bound(pre+1,pre+N+1,W)-pre;
printf("%lld\n",p[ansp].low);
return 0;
}
T2 货物分组
题目
考场思路
定义 :第 个物品及之前划分出 个背包
那么状转时间复杂度 。
考虑到从后往前 ,再使用单调栈优化,然后…没时间码了…
考场代码:
#include<cstdio>
#include<cstring>
#define int long long
#define rep(__i,__a,__b) for(int __i=__a,__i##_end_=__b;__i<=__i##_end_;++__i)
#define dep(__i,__a,__b) for(int __i=__a,__i##_end_=__b;__i>=__i##_end_;--__i)
#define lc (i<<1)
#define rc (i<<1|1)
template<class T>inline void qread(T& x){
char c;bool f=false;x=0;
while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int rqread(){
char c;bool f=false;int x=0;
while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return f?-x:x;
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline void getInv(int inv[],const int r,const int MOD)
{inv[0]=inv[1]=1;for(int i=2;i<=r;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;}
const int MAXN=5000;
const int INF=1ll<<60;
int N,W,a[MAXN+5],dp[MAXN+5][MAXN+5],pre[MAXN+5];
signed main(){
qread(N,W);
rep(i,1,N)qread(a[i]),pre[i]=pre[i-1]+a[i];
for(int i=0;i<=N;++i)for(int j=0;j<=N;++j)dp[i][j]=INF;
dp[0][0]=0;
int maxx,minn;
rep(i,1,N)rep(j,1,i){
maxx=minn=a[i];
dep(k,i-1,0){//从 k+1 到 i 是一个新的背包
//i:当前点
//j:打包数量
//k:上一个打包的起点
if(pre[i]-pre[k]>W)break;//背包爆掉
dp[i][j]=Min(dp[i][j],dp[k][j-1]+j*(pre[i]-pre[k])+(maxx-minn));
maxx=Max(maxx,a[k]);
minn=Min(minn,a[k]);
}
}
// rep(i,1,N){
// printf("Now i==%-20lld\n",i);
// printf("debug:>arr : ");
// rep(j,1,i)printf("%-20lld ",dp[i][j]);
// puts("");
// }
int ans=INF;
rep(j,1,N)ans=Min(ans,dp[N][j]);
printf("%lld\n",ans);
return 0;
}
题解
60pts 算法:一维 DP
我们的状态是二维的,转移是一维的,考虑怎么降维进行 。
考虑压缩状态,这里有一道题十分类似:小奇探险
这道题有什么共通之处呢?前面的选择都会影响后面选择的价值。
而 小奇探险
选择倒着 ,每次前面选择都再 *k
就处理好后面的选择因为前面选择而造成的影响。
这道题也可以用类似的思想,就可以剪掉一层循环。
定义 :前 个点分了一些组,最后一个组以 为结尾时的最小花费。
而这个 还要算上后面的花费,不清楚?看看状转:
后面的 求得就是前面的选择对于后面的花费造成的影响。
这样,我们就剪掉一维,实现 的算法了。
100pts 算法:一维 DP ?线段树 + 单调栈 优化 !
我们设 。
Q
为什么没有 部分?
因为每个 都会加上一个求和部分。
所以对于 数组来说,它是平等的,只需在最后都加上 部分即可。
那么,对于每一个 来说,状转即可化简为:
对于这个状态转移,似乎是没有什么问题的,但是当你实现起来,就会发现,这个 中涉及的 似乎是很难求出来的,如果要使用状态转移,就还需要一个线段树用一个 来询问区间最值。
但是为什么题目要挂一个单调栈的优化呢?这是老师的优化,但是我觉得多此一举。
维护一个单调递增的维护最大值的单调栈。
维护一个单调递减的维护最小值的单调栈。
有什么用呢?每当 更改的时候,所有的对于这个维度的 都会因为最大值最小值因为 的更改而更改,所以,单调栈变动时,用线段树区间修改 的值即可。
但既然都用线段树区间修改了,何不就只用线段树解决最大最小值呢?
这还多了一个单调栈的优化,我蒟蒻认为似乎有些麻烦。
没时间补题啊
T3 地形计算
题目
考场思路
考试的时候 打到一半就走了,这道题都没来得及看。
正解
首先想暴力算法。
30pts 算法
暴力枚举四个点,再进行判断,细节不用做过多赘述,时间复杂度 。
显然可以拿到 的高分 老师是这么说的
60pts 算法
那么他为什么要给 的数据呢?
肯定是有算法呗。
只需枚举两条边,再匹配这两条边的四个点即可,这样的时间复杂度是 。
这样是可以得 的。
出题人说还有一个玄学情况,时间复杂度是 ,这种也是有 的。
100pts 算法
似乎已经没有更好的方法了,但是我们考虑对于60分的做法,我们切换枚举顺序,从度数最大的点开始枚举。每次枚举完毕以后,删去度数最大的点,然后重复这个过程,也能统计出答案。正确性显然,我们考虑这个做法的复杂度。每次都使 减小 ,那么复杂度为 看起来没什么用,只是多了一个的常数。
但是因为Venn在这种做法上施加了魔法,所以复杂度是正确的。
其实我们上述复杂度的分析,给出的复杂度上界达不到,我们考虑 并不是他的时间复杂度。我们换一种分析方式,对于选择的每一个度数小于 的点,他向外会扩展最多 次。对于度数大于 的点,他会向外扩展多次,但是由于所有点的度数之和不能超过 倍的 ,所以单次均摊是 次扩展,这样的点不会超过 个,因为所有点的度数之和等于 。那么总时间复杂度就为
我觉得这一段写得还不错,直接摘录了
没时间补题啊