2.【LGR-148-Div.3】洛谷基础赛 #1 & MGOI Round I
【LGR-148-Div.3】洛谷基础赛 #1 & MGOI Round I
据说是普及组难度?
T1 P9502 『MGOI』Simple Round I | A. 魔法数字
『MGOI』Simple Round I | A. 魔法数字
\(100pts\)
题目描述
初级魔法士小 M 的魔法数字是 \(2\)。
给定一个正整数 \(n\),小 M 需要找到最大的 偶数 \(m\),使得 \(2^m<n\)。
- 又双叒叕是个水题,
然后被又双叒叕水题爆切。。。。。。 - 只要判断 \(log2(n)\%2\) 是否为偶数,并且不能正好是 \(2\) 的 \(n\) 次方,然后输出即可。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,i;
cin>>n;
if(int(log2(n))%2==0&&int(log2(n))!=log2(n))
cout<<(int)log2(n);
else if(int(log2(n))%2==1)
cout<<(int)log2(n)-1;
else
cout<<int(log2(n)-2);
}
T2 P9503 『MGOI』Simple Round I | B. 魔法照相馆
\(100pts\)
题目描述
小 M 正在准备入学所必需的魔法士证件,因此他来到了纵深巷的魔法照相馆。
在等待的时候,小 M 注意到魔法照相馆有三个幕布,颜色从左到右分别是红色、蓝色和白色。店主 zx 先生会根据客人的需求拉上或拉下这三个幕布,如下图所示:
幕布摆放在左边,按照红蓝白的顺序排列。人则坐在幕布右边,而 zx 先生则站在最右边给人拍照。幕布从右往左数,第一个没有拉上的幕布颜色将成为照片的背景颜色。
每次 zx 先生拉上或拉下一个幕布都需要消耗一个时间单位,而拍照不消耗时间。小 M 已经知道了他前面所有总共 \(n\) 个客人的需求。在初始状态为 全部拉下 的情况下,请你帮助小 M 计算他至少需要等待多少个时间单位。
- 水题,只要模拟状态,再对号入座,就轻轻松松 \(AC\) 了。
- (其中红色幕布不会拉上去)
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,i,sum=0,zt=111;
char q;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>q;
//111 110 101 100
if(q=='W'&&(zt==101||zt==111))continue;
else if(q=='B'&&(zt==110))continue;
else if(q=='R'&&(zt==100))continue;
if(q=='W')
{
if(zt==110)zt=111,sum++;
else if(zt==100)zt=101,sum++;
}
if(q=='B')
{
if(zt==111||zt==100)zt=110,sum++;
else if(zt==101)zt=110,sum+=2;
}
if(q=='R')
{
if(zt==111)zt=100,sum+=2;
else if(zt==110||zt==101)zt=100,sum++;
}
}
cout<<sum;
}
- 将各个幕布的状态枚举,拉下为 \(1\) ,拉上为 \(0\) 。
T3P9504 『MGOI』Simple Round I | C. 魔法禁林
\(30pts\)
题目描述
开学的第一天,小 M 迫不及待地计划着前往神秘的禁林。
小 M 拥有两个重要的属性,魔力值和生命值。非常特别的是,初始时,这两个值可以由小 M 任意决定。
禁林可以看作一张 \(n\) 个点 \(m\) 条边的无向简单连通图。小 M 将在禁林里面行走,从起点 \(s\) 走到 \(t\)。
每经过一条边,小 M 的魔力值都会减去 1。同时,每条边上有一个具有攻击力属性的魔兽,小 M 要与之战斗。若小 M 经过这条边之前的魔力值为 \(k\),这条边上魔兽的攻击力为 \(w\),那么经过这条边时发生的战斗将会消耗 \(\left\lfloor \dfrac{w}{k} \right\rfloor\) 的生命值。魔兽不会被打败,因此多次经过同一条边,每次都会发生战斗。
小 M 需要保证,当他的魔力值消耗完时,他的生命值为 0,且此时走到 \(t\) 点。
你需要求出小 M 初始时需要的最小生命值。
【数据范围】
对于所有数据,\(1 \le n \le 20000\),\(1 \le m \le 40000\),\(1\le s,t,u,v\le n\),\(s\ne t\),图为无向简单连通图,\(0\le w\le 100\)。
Subtask | \(n\) | \(m\) | \(w\le\) | 分值 |
---|---|---|---|---|
\(1\) | \(5\) | \(10\) | \(10\) | \(11\) |
\(2\) | \(2000\) | \(4000\) | \(10\) | \(27\) |
\(3\) | \(20000\) | \(40000\) | \(1\) | \(19\) |
\(4\) | \(20000\) | \(40000\) | \(100\) | \(43\) |
- 一开始觉得完全打不了,但是还是试了试。
- 首先使用 \(dijkstra\) 求最短路,但是不知道走每条边的状态,后来想到倒推,以终点 \(t\) 为起点,推回起点 \(s\) 。
\(30pts\)代码
#include<bits/stdc++.h>
using namespace std;
int n,m,s,t;
struct aa
{
int nxt,to,w;
}e[1100001];
int cnt=0,head[100001];
void add(int u,int v,int w)
{
e[++cnt]={head[u],v,w};
head[u]=cnt;
}
long double dis[100011];
int stp[100011];
int vis[100011];
struct cmp
{
bool operator() (int &a,int &b) const
{
return dis[a]>dis[b];
}
};
void dijkspfa(int x)
{
priority_queue<int,vector<int>,cmp>q;
int i,j,k;
memset(vis,0,sizeof(vis));
for(i=1;i<=n;++i)dis[i]=0x7f7f3f3f3f3f3f,stp[i]=0;
q.push(x);
dis[x]=0;
while(!q.empty())
{
k=q.top(),q.pop();
if(!vis[k])
{
vis[k]=1;
for(i=head[k];i;i=e[i].nxt)
{
int to=e[i].to;
if(dis[to]>=dis[k]*1.0+e[i].w/(stp[k]+1.0))
{
stp[to]=stp[k]+1;
dis[to]=dis[k]*1.0+e[i].w/stp[to]*1.0;
q.push(to);
}
}
}
}
}
int main()
{
int i,j,k,u,v,w;
cin>>n>>m>>s>>t;
for(i=1;i<=m;i++)
{
cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
}
dijkstra(t);
cout<<(int)(ceil(dis[s]));
}
(虽然试试就逝世,但是出题人的数据怎么如此之氵,这也能骗30分)...
测评记录
- 实际上,生命值不是 \(double\) 而是 \(int\)。 \(int\)有什么特性,自动向下取整。于是当一个数除以一个大于它的数时,值为 \(0\) 。
- 因此可以让每条边被经过不超过 \(100\) (\(0<=w<=100\))遍。当一条边被经过 \(100\) 次时,求现有最小值与其值的最小值。最后输出即可
- (
话说 \(dijkspfa\) 是什么东西,怎么这么强。。。)。
附上\(AC\)代码
#include<bits/stdc++.h>
using namespace std;
int n,m,s,t;
struct aa
{
int nxt,to,w;
}e[1100001];
struct ee
{
int cnt,x;
}k;queue<ee>q;
int cnt=0,head[100001];
void add(int u,int v,int w)
{
e[++cnt]={head[u],v,w};
head[u]=cnt;
}
int dis[222][230011];
bool vis[222][230011];
int minn=0x7f7f7f00;
void dijkstra(int x)
{
int i,j;
memset(vis,0,sizeof(vis));
for(i=1;i<=101;++i)for(j=1;j<=n;++j)dis[i][j]=0x7f7f7f7f;
q.push({1,x});
dis[1][x]=0;
int v=0;
while(!q.empty())
{
k=q.front(),q.pop();
int y=k.cnt;
int p=k.x;
if(y>100)
{
minn=min(minn,dis[y][p]);
}
for(i=head[p];i;i=e[i].nxt)
{
int to=e[i].to;
if(dis[y+1][to]>=dis[y][p]*1.0+e[i].w/y)
{
dis[y+1][to]=dis[y][p]*1.0+e[i].w/y;
if(!vis[y+1][to])
q.push({y+1,to}),vis[y+1][to]=1;
}
}
}
}
int main()
{
int i,j,k,u,v,w;
cin>>n>>m>>s>>t;
for(i=1;i<=m;i++)
{
cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
}
dijkstra(t);
for(i=1;i<=101;i++)minn=min(minn,dis[i][s]);
cout<<minn;
}
T4P9505 『MGOI』Simple Round I | D. 魔法环
\(0pts\) 一分没骗到。。。
- 赛后我们首先
查看题解,原来本蒟蒻与大佬之间的差距如此之大。。 - 由于精灵围成了一个环,因此首先考虑破环为链
-
状态转移方程\(\LARGE \textcolor {#9624f6} {(由大佬的题解得出)}\)
-
\(\Large f_{i,j}=\displaystyle\min_{k=1}^{i-1}f_{\substack{k,j-1}}+getv(k,i)\)
-
\(getv(l,r)\)表示这一步激活\(r\)点,上一个激活\(l\)点使当前值变化的量。
-
\(\Large getv(l,r)=\cfrac{(r-l+1)(r-l)}{2}\times \max(a_{l},a_{r})+a{^2_r}\)
-
这时复杂度为\(\large O(n^4)\),轻轻松松\(\Huge \textcolor {#111111} {TLE}\),观察状态转移方程可知,其于\(j\)即已激活点数无关,所以如果\(\large j\ge k\)时可以直接从\(\large f{_k} { _,} {_j}\)转移,此时复杂度为\(\large O(n^3k)\)
-
还有一个结论,即一定激活点\(0\)。因为如果未激活点\(0\),由于至少要激活\(k(2 \leq k \leq 100)\)个精灵,因此未激活的精灵总能找到魔力值大于\(0\)的精灵。因此点\(0\)未产生贡献(
甚至负贡献)。 -
所以直接由\(0\)为起点,而此时复杂度为\(\large O(n^2k)\),可以通过此题。
- 关于大佬们的破环为链
for(int i=1;i<=n;i++) scanf("%d",&q[i]);
for(int i=1;i<=n;i++) if(!q[i]) pos=i;
for(int i=1;i<=n;i++) p[i]=q[(i+pos-1-1)%n+1];
关于我的破环为链
for(i=0;i<n;++i)
{
cin>>a[i];
if(!a[i])res=i;
}
for(i=res,j=1;j<=n;++i,++j)
{
s[j]=a[i];
if(i==n-1)i=-1;
}
(明显不如原神)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,s[3011],t,ans=0x7f7f7f7f,p;
int f[3022][111],a[3011];
inline int getv(int l,int r)
{
return ((r-l)*(r-l-1)>>1)*max(s[l],s[r])+s[r]*s[r];
}
signed main(void)
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
register int i,j,k;
int u,v,w,res;
cin>>n>>m;
for(i=0;i<n;++i)
{
cin>>a[i];
if(!a[i])res=i;
}
for(i=res,j=1;j<=n;++i,++j)
{
s[j]=a[i];
if(i==n-1)i=-1;
}
memset(f,0x7f,sizeof(f));
f[1][1]=0;
for(i=1;i<=n;++i)
for(j=2;j<=min(i,m);++j)
for(k=1;k<i;++k)
{
f[i][j]=min(f[i][j],f[k][j-1]+getv(k,i));
if(j==m)f[i][j]=min(f[i][j],f[k][j]+getv(k,i));
}
ans=LLONG_MAX;
for(i=1;i<=n;++i)ans=min(ans,f[i][m]+getv(i,n+1));
cout<<ans;
}
- dp数组求最小值一定要初始化为\(\infin\),并且将始状态赋值为0。