天天快乐编程2020年OI集训队 训练9题解
本次主要是分治以及最短路。一定要注意赛后的补题,把自己会的题做完了,赛后不管了,那么这场训练毫无意义,要去积极尝试自己不会的,不懂的问老师。
1.4839: 麦森数
NOIP2003普及组T1
2p与2p-1有着相同的位数,2的p次方末位一定不为0,减1位数不会改变,那我们可以直接求2^p的位数,位数为log10(2*p)+1
然后就是求后500位了,裸的高精度快速幂
快速幂也是分治思想,把它和二分联系起来
#include<bits/stdc++.h>
using namespace std;
int a[510]={2};
#define M 500
void mul()
{
for(int i=0;i<M;i++)
a[i]*=2;
for(int i=0;i<M;i++)
a[i+1]+=a[i]/10,a[i]%=10;
}
void square()
{
int b[510]={0};
for(int i=0;i<M;i++)
for(int j=0;j<M;j++)
if(i+j<M)
b[i+j]+=a[i]*a[j];
for(int i=0;i<M;i++)
b[i+1]+=b[i]/10,b[i]%=10;
for(int i=0;i<M;i++)
a[i]=b[i];
}
void quickmi(int x)
{
if(x/2>1) quickmi(x/2);
square();
if(x%2) mul();
}
int main()
{
int p;
scanf("%d",&p);
printf("%d\n",(int)(log10(2.0)*p)+1);
quickmi(p);
a[0]--;
for(int i=M-1;i>=0;i--)
printf("%d",a[i]);
}
2.6020: 扫雷游戏
NOIP2015 普及组T2
这个题目不需要用到分治,我们直接直接按照雷进行循环,模拟就行了
注意下标不要越界啊
#include<bits/stdc++.h>
using namespace std;
//有雷为1,无雷为0
bool a[105][105];
int main()
{
int n,m;
char tmp;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>tmp;
//如果是地雷就将这个点设为1
if(tmp=='*') a[i][j]=1;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i][j]==1)
{
printf("*"); //如果是地雷不用输出数字
}
else
{
printf("%d",a[i+1][j+1]+a[i+1][j-1]+a[i+1][j]+a[i][j+1]+a[i][j-1]+a[i-1][j+1]+a[i-1][j]+a[i-1][j-1]);
}
}
printf("\n");
}
return 0;
}
3.6272: 龙虎斗
NOIP2018 普及组T2
给出n个数,再给出一个m位置,定义a和b,(m不属于a或b):a为1m-1个数每个数用这个数到m的距离乘这个数的积之和,b则为m+1n中再在这n个每个数用这个数到m的距离乘这个数的积之和,然后在其中一个位置(p1)加入了一个数(s1),现在再给你一个数(s2),求这个数加在哪一个位置能让a和b的差最小(加上去这个数的计算方法和之前相同)
我们可以枚举举1~m-1(即a区间)内的位置去找最小值,当然记得开ll,十年OI一场空,不开long long见祖宗。
#include <bits/stdc++.h>
using namespace std;
long long a[100005], s1, s2, sum2, sum1, minn;
int m, n, p1, ans;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
cin >> m >> p1 >> s1 >> s2;
a[p1] += s1;
for (int i = 1; i < m; i++)
sum1 += a[i] * (m - i);
for (int i = m + 1; i <= n; i++)
sum2 += a[i] * (i - m);
minn = abs(sum1 - sum2);
ans = m;
for (int i = 1; i < m; i++)
if (abs(sum1 + s2 * (m - i) - sum2) < minn)
{
minn = abs(sum1 + s2 * (m - i) - sum2);
ans = i;
}
for (int i = m + 1; i <= n; i++)
if (abs(sum2 + s2 * (i - m) - sum1) < minn)
{
minn = abs(sum2 + s2 * (i - m) - sum1);
ans = i;
}
cout << ans;
}
4.4866: 瑞士轮
NOIP2011普及组T3
这个题有一定难度
每组比赛的胜者:赛前,总分是按降序排的;获胜后都得1分,仍是降序;
每组比赛的负者:赛前,总分是按降序排的;不得分,仍是降序。
先按初始分数排序,然后按分数高低两人一组比赛;
胜者入队A,负者入队B。这样A、B自身仍是有序的;只需进行合并操作即可。
这不就是归并排序吗?说实话,这个题确实高。
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
struct point
{
int num, id, w;
bool operator<(const point &tmp) const
{
if (num != tmp.num)
return num > tmp.num;
return id < tmp.id;
}
} a[N >> 1], b[N >> 1], tmp[N];
int n, r, q;
void merge()
{
int i = 1, j = 1, k = 1;
while (i <= n && j <= n)
{
if (a[i].num > b[j].num || a[i].num == b[j].num && a[i].id < b[j].id)
tmp[k++] = a[i++];
else
tmp[k++] = b[j++];
}
while (i <= n)
tmp[k++] = a[i++];
while (j <= n)
tmp[k++] = b[j++];
}
int main()
{
scanf("%d%d%d", &n, &r, &q);
for (int i = 1; i <= 2 * n; i++)
scanf("%d", &tmp[i].num), tmp[i].id = i;
for (int i = 1; i <= 2 * n; i++)
scanf("%d", &tmp[i].w);
sort(tmp + 1, tmp + 2 * n + 1);
for (int t = 1; t <= r; t++)
{
int cnt = 1;
for (int i = 1; i < 2 * n; i += 2)
{
if (tmp[i].w > tmp[i + 1].w)
a[cnt] = tmp[i], b[cnt] = tmp[i + 1], a[cnt++].num++;
else
a[cnt] = tmp[i + 1], b[cnt] = tmp[i], a[cnt++].num++;
}
merge();
}
printf("%d\n", tmp[q].id);
return 0;
}
5.5914: 铺设道路
NOIP2018提高组Day1T1
它就是一个贪心。
题目里给的样例是4,3,2,5,3,5;
可以选择一个区间进行“填坑”操作;
所以我们的贪心策略是:
若a[i]>a[i-1],计数器sum+=a[i]-a[i-1];
那么为什么这样贪心是对的呢?
贪心证明
假设现在有一个坑,但旁边又有一个坑。
你肯定会选择把两个同时减1;
那么小的坑肯定会被大的坑“带着”填掉。
大的坑也会减少a[i]-a[i-1]的深度,可以说是“免费的”;
所以这样贪心是对的;
#include <bits/stdc++.h>
using namespace std;
int a[100001];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
}
int sum = 0;
for (int i = 1; i <= n; ++i)
{
if (a[i] > a[i - 1])
{
sum += a[i] - a[i - 1];
}
}
printf("%d\n", sum);
}
6.4811: 最优贸易
NOIP2009提高组T2
可以考虑一下单源最短路径。 假设我们是阿龙的话,当然会在路径上选择最便宜的地方买物品,最贵的地方售出物品。
如果对Dijkstra的更新方式进行修改,就可以求出从源点到每一个点购入的最便宜价格。
但是怎么搞呢,两次Dijkstra,再枚举ans=max(售出价格[i]-购入价格[i])即可求出答案。当然第二次Dijkstra要求进行在反图上
#include <bits/stdc++.h>
using namespace std;
const int M = 500010;
const int N = 100010;
int dist1[N], dist2[N], vis1[N], vis2[N];
vector<int> e1[N], e2[N];
struct pt1
{
int u, dis;
bool operator>(const pt1 &t) const { return dis > t.dis; }
} p1[N];
struct pt2
{
int u, dis;
bool operator<(const pt2 &t) const { return dis < t.dis; }
} p2[N];
priority_queue<pt1, vector<pt1>, greater<pt1>> q1;
priority_queue<pt2, vector<pt2>, less<pt2>> q2;
int n, m, money[N];
void dj1()
{
for (int i = 1; i <= n; i++)
{
dist1[i] = money[i];
}
q1.push((pt1){1, dist1[1]});
while (!q1.empty())
{
int u = q1.top().u;
q1.pop();
if (vis1[u])
continue;
vis1[u] = 1;
for (int i = 0; i < e1[u].size(); i++)
{
int v = e1[u][i];
if (dist1[v] > dist1[u])
{
dist1[v] = dist1[u];
q1.push((pt1){v, dist1[v]});
}
if (vis1[v] == 0)
q1.push((pt1){v, dist1[v]});
}
}
}
void dj2()
{
for (int i = 1; i <= n; i++)
dist2[i] = money[i];
q2.push((pt2){n, dist2[n]});
while (!q2.empty())
{
int u = q2.top().u;
q2.pop();
if (vis2[u])
continue;
vis2[u] = 1;
for (int i = 0; i < e2[u].size(); i++)
{
int v = e2[u][i];
if (dist2[v] < dist2[u])
{
dist2[v] = dist2[u];
q2.push((pt2){v, dist2[v]});
}
if (vis2[v] == 0)
q2.push((pt2){v, dist2[v]});
}
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> money[i];
for (int i = 1; i <= m; i++)
{
cin >> u >> v >> op;
if (op == 1)
e1[u].push_back(v), e2[v].push_back(u);
else
{
e1[u].push_back(v);
e2[v].push_back(u);
e1[v].push_back(v);
e2[u].push_back(v);
}
}
dj1();
dj2();
int ans = 0;
for (int i = 1; i <= n; i++)
{
if (vis1[i] && vis2[i])
ans = max(ans, dist2[i] - dist1[i]);
}
printf("%d", ans);
return 0;
}
7.4804: 树网的核
NOIP2007提高组T4
在直径上尺取,计算每条路径的偏心距
1、 通过Floyd算法求出顶点对之间的最短路径,并记录下具体的最短路径。
2、 标记所有在直径上的点。
3、 枚举路径的起点,进行DFS穷举路径的终点。
4、 对于一条确定下来的路径,通过直接模拟的方法得出偏心矩。
#include<bits/stdc++.h>
using namespace std;
const int maxn=305;
int G[maxn][maxn];
int main()
{
int n,s;
scanf("%d%d",&n,&s);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j)G[i][j]=0;
else G[i][j]=0x3f3f3f3f;
for(int i=1,u,v,w;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
G[u][v]=G[v][u]=min(G[u][v],w);
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
G[i][j]=min(G[i][j],G[i][k]+G[k][j]);
int ans=1e9;
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
if(G[i][j]>s)continue;
int maxx=0;
for(int k=1;k<=n;k++)
maxx=max(maxx,(G[i][k]+G[j][k]-G[i][j])/2);
ans=min(ans,maxx);
}
printf("%d\n",ans);
return 0;
}
本文来自博客园,作者:暴力都不会的蒟蒻,转载请注明原文链接:https://www.cnblogs.com/BobHuang/p/13830053.html