2021.11.4模拟总结
前言
崩盘。。。\(30+10+0+20=60pts\)
找一下原因吧。
大概是因为把 \(T1\) 想的过于复杂,时间长。
加上略微有点困,看完 \(T1\) 就有点没有动力了,感觉整场考试都是半麻的状态(没有那种世俗的欲望.jpg)。
当然这肯定是不行的,想不出来的时候不妨换个思路嘛,这种把精神状态都搭进去的思考就没太大价值了。
以至于 \(T4\) 最后已经想到了关键点,但是就是由于思维不够活络而没进一步思考。
真的需要提高兴奋度,专注度,紧张度。
算是吸取了经验教训吧。
T1 coin
题意:宽度为 \(k\) 的爪子抓硬币,不能抓空。
分析
最 TM 离谱的题,这么简单的 DP 我都没做出来。
一开始也想到 DP,但是转移的时候本来想的是从前面的所有位置转移过来,但是这样会重复;又想从 \(i-k\) 的位置转移过来,又会漏算中间的部分。于是我就以为这个后效性没法处理,于是想别的思路,然后就 gg 了。
实际上,只有 \(\mod\ k\) 相同的位置互相有影响,奇数个就少选一个最小的,偶数个就全选(这甚至不需要 DP)。
但其实 DP 也可以,只需要把中间的部分从 \(i-1\) 继承过来即可,相当于前缀和的思想。
其实方法还有很多,但我一个都没有想到QAQ
代码
点击查看代码
#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin), freopen(a".out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f, N = 1e5+5;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
ll ret = 0; char ch = ' ', c = getchar();
while(!(c >= '0' && c <= '9')) ch = c, c = getchar();
while(c >= '0' && c <= '9') ret = (ret << 1) + (ret << 3) + c - '0', c = getchar();
return ch == '-' ? -ret : ret;
}
int n,m;
ll a[N];
ll dp[N][2];
int len;
signed main(){
//fo("coin");
n = read(), m = read();
for(int i = 1 ; i <= n ; i ++)
a[i] = read();
for(int i = m+1 ; i <= n ; i ++){
dp[i][0] = dp[i][1] = max(dp[i-1][0],dp[i-1][1]);
dp[i][1] += -max(dp[i-m][0],dp[i-m][1]) + dp[i-m][0] + a[i] + a[i-m];
}
printf("%lld",max(dp[n][0],dp[n][1]));
}
/*
3 2
1 2 3
-------
5 2
1 2 3 2 1
*/
T2 maze
分析
已经想到以每个点为圆心画圆,使得上下连起来的思路。
然而对于建图出现了瓶颈。
实际上,借用最小生成树的思路,从上边界为起点,直到碰到下边界为止,最大的边就是答案。
不过,生成的可能不是树,只是借用了这种思想。
由于边数有 \(n^2\) 级别,所以要用 Prim,而且不能加堆优化,甚至不能存图(空间不够)
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f,N = 6e3+10;
#define ll long long
#define FCC fclose(stdin),fclose(stdout)
inline ll read()
{
ll ret=0;char ch=' ',c=getchar();
while(!(c>='0'&&c<='9')) ch=c,c=getchar();
while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
return ch=='-'?-ret:ret;
}
int n,m,k;
ll x[N],y[N];
inline double sq(int i,int j)
{
return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}
int head[N],ecnt=-1;
void init_edge(){memset(head,-1,sizeof(head)),ecnt=-1;}
struct edge
{
int nxt,to;
double w;
}a[N*N<<1];
inline void add_edge(int x,int y,double w)
{
a[++ecnt]=(edge){head[x],y,w};
head[x]=ecnt;
}
bool vis[N];
double dis[N],ans;
void prim()
{
for(int i=1;i<=k+1;i++) dis[i]=1e18;
dis[0]=0;
while(1)
{
double minn=1e18;
int u=0;
for(int i=0;i<=k+1;i++)
{
if(dis[i]<minn&&!vis[i]) minn=dis[i],u=i;
}
//puts("");
if(minn==1e18) break;
vis[u]=1;
ans=max(ans,minn/2);
//printf("u=%d\n",u);
if(u==k+1) {printf("%.8lf",ans);return;}
vis[u]=1;
for(int i=head[u];~i;i=a[i].nxt)
{
int v=a[i].to;
if(dis[v]>a[i].w)
{
dis[v]=a[i].w;
// if(!vis[v]) q[++cnt]=v;
}
}
}
}
signed main()
{
n=read(),m=read(),k=read();
init_edge();
for(int i=1;i<=k;i++)
{
x[i]=read(),y[i]=read();
add_edge(0,i,m-y[i]),add_edge(i,0,m-y[i]);
add_edge(k+1,i,y[i]),add_edge(i,k+1,y[i]);
}
for(int i=1;i<=k;i++)
for(int j=1;j<i;j++)
{
add_edge(i,j,sq(i,j)),add_edge(j,i,sq(i,j));
// printf("%.8lf\n",sq(i,j));
}
prim();
return 0;
}
T4 锦标赛
分析
其实这题真的很好想暴力,首先排序,然后就知道可以区间 DP 了。
但是我压根没往这方面想,净想贪心,数据结构之类了(麻了)
记 \(dp(l,r,k)\) 表示用 \(k\) 天解决区间的最小代价,转移也就很显然了。
正解是决策单调性优化,并不容易。但是暴力没想出来属实是不应该,可能一方面不够扎实,一方面钻牛角尖了。
暴力代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f,N = 1e3+10;
#define ll long long
#define FCC fclose(stdin),fclose(stdout)
inline ll read()
{
ll ret=0;char ch=' ',c=getchar();
while(!(c>='0'&&c<='9')) ch=c,c=getchar();
while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
return ch=='-'?-ret:ret;
}
int n,k,a[N];
int dp[N][N][51];
int solve(int l,int r,int d)
{
if(l==r) return 0;
if(!d) return INF;
if(dp[l][r][d]) return dp[l][r][d];
dp[l][r][d]=INF;
for(int i=l;i<r;i++)
{
dp[l][r][d]=min(dp[l][r][d],solve(l,i,d-1)+solve(i+1,r,d-1)+a[r]-a[i]);
}
return dp[l][r][d];
}
int main()
{
n=read(),k=read();
for(int i=1;i<=n;i++) a[i]=read();
sort(a+1,a+n+1);
printf("%d\n",solve(1,n,k));
return 0;
}
/*
4 3
1 90 91 92
18 7
67 64 52 18 39 92 84 66 19 64 1 66 35 34 45 2 79 34
*/