天天快乐编程杯中学生信奥联赛(2020年暑假赛)题解

本次题目难度顺序基本从难到易,在这里非常感谢出题组同学的真情付出。博客的阅读体验可能更好https://www.cnblogs.com/BobHuang/p/13586397.html

1.6321: Alice与素数

这个题目主要想让你们了解素数相关背景,直接输入,然后输出即可通过

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    cin>>n;
    cout<<"Hello, prime "<<n<<"!\n";
}

2.6319 Alice与字母K

我们可以观察下字母K,主要是他的空格个数需要我们控制
以n=3为例,第零行需要输出2个空格,第一行需要输出1个空格,第二行需要输出0个空格,第i行需要输出n-i-1个空格。
\属于特殊的字符,即转义字符,需要两个

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cout<<"|";
        for(int j=1;j<n-i;j++)cout<<" ";
        cout<<"/\n";
    }
    for(int i=n-1;i>=0;i--)
    {
        cout<<"|";
        for(int j=1;j<n-i;j++)cout<<" ";
        cout<<"\\\n";
    }
    return 0;
}

3.6223: Alice与签到题

这个题目考察int除法、多组输入和字符串读入。
这个题属于TZOJ1076: 输入入门(1) ,直接while读入就好
除法可以直接用double读入,int读入要进行转换
读取一行可以使用gets或者getline,每个字符串前后都有个空格,可以用getchar()或cin.get()吃掉或者将总长度-1
gets的代码

#include <bits/stdc++.h>
using namespace std;

int main()
{
	int n;
	while(cin >> n)
	{
		if(n)
		{
			char s[100005];
			gets(s);
            int l=0;
			for(int i=0;s[i];i++)l++;
			cout << l-1 << endl;
		}
		else
		{
			double a, b;
			cin >> a >> b;
			printf("%.2lf\n", a/b);
		}
	}
	return 0;
}
#include <bits/stdc++.h>
using namespace std;
int main()
{
    int op;
    while (cin>>op)
    {
        if (op==0)
        {
            int a,b;
            cin>>a>>b;
            printf("%.2f\n",a*1./b);
        }
        else
        {
            cin.get();
            string s;
            getline(cin,s);
            cout<<s.length()<<"\n";
        }
    }
}

4.6318: sleep函数

Fsleep(x)就是x-与他互质的数,要求最小,那不是求1…n中,与n互质的数的数量最多,这就是欧拉函数。
证明也需要用到他,phi(n)表示1…n中,与n互质的数的数量,
数学符号为\(\varphi(n)\)。n为素数时, \(\varphi(n) = n-1\);n为合数时
利用唯一分解定理,我们可以把一个整数唯一地分解为质数幂次的乘积,
\(n =p_1^{k_1} \times p_2^{k_2}\times...\times p_m^{k_m}\) ,其中 \(p_i\) 是质数,那么 \(\varphi(n) = n \times {\dfrac{p_1 - 1}{p_1}}\times{\dfrac{p_2 - 2}{p_2}}\times...\times{\dfrac{p_m - 1}{p_m}}\)
只要有一个因子\(p_i\),那么他会让其少\(n\div p_1\) 个因子,所以肯定是最大的素数\(\varphi(n)\) 最大。
欧拉函数的相关知识可以参考OI-wiki
当然我们也可以使用找规律找到答案,要不然为什么放在前面呢。我们倒着找最大的素数即可,即使正着也是可以通过的

#include <bits/stdc++.h>
using namespace std;
bool is_prime(int x)
{
    for (int i = 2; i * i <= x; i++)
    {
        if (x % i == 0)
            return false;
    }
    return true;
}
int main()
{
    int n, ans;
    cin >> n;
    for (int i = n;; i--)
    {
        if (is_prime(i))
        {
            cout << i << "\n";
            return 0;
        }
    }
}

5.6316: Alice与参赛队伍选拔

这个题目就是结构体排序,规则参考NOI金牌的规则。NOI金牌允许并列,集训队名额固定。
我们可以先按照名次排序,金牌就是得到第二个同学的成绩,然后输出。但是这样输出对吗?我们需要得到第二个同学的成绩后,再对他们的输入顺序进行排序,也就是我们再设置一个ID。集训队最后输出只需要对前5个进行排序。

#include <bits/stdc++.h>
using namespace std;
struct T
{
    string name;
    int score1,score2,grade;
    int id;
}a[7];
int cmp1(T a,T b)
{
    if(a.score1!=b.score1)return a.score1>b.score1;
    if(a.score2!=b.score2)return a.score2>b.score2;
    return a.grade<b.grade;
}
int cmp2(T a,T b)
{
    return a.id<b.id;
}
int main()
{
    int n=7;
    for(int i=0;i<n;i++)
    {
        cin>>a[i].name>>a[i].score1>>a[i].score2>>a[i].grade;
        a[i].id=i;
    }
    sort(a,a+n,cmp1);
    int t=a[1].score1;
    sort(a,a+n,cmp2);
    cout<<"Gold:\n";
    for(int i=0;i<n;i++)
    {
        if(a[i].score1>=t)cout<<a[i].name<<"\n";
    }
    sort(a,a+n,cmp1);
    sort(a,a+5,cmp2);
    cout<<"\nTeam:\n";
    for(int i=0;i<5;i++)
    {
        cout<<a[i].name<<"\n";
    }
}

6.6317: Alice与整除

感谢linwenqi发现题目错误
这个题目就是绕了一下整除,其实就是求N的最小倍数M。我们可以把6个数字的全排列求出来,然后深搜得到所有情况,依次判断。0是特殊的,对它取余会RE,需要特判掉。
这个题目还有个升级版,必须使用BFS才能通过,通过的同学可以尝试下TZOJ3031: Multiple

#include <bits/stdc++.h>
using namespace std;
int N, m, a[10];
int ans;
void dfs(int cur,int t)
{
    if (t&&t % N == 0)
    {
        if(ans)ans=min(ans,t);
        else ans=t;
    }
    if(cur==m)return;
    dfs(cur+1,t*10+a[cur]);
    dfs(cur+1,t);
}
void gao()
{
    ans=0;
    sort(a, a + m);
    do
    {
       dfs(0,0);
    } while (next_permutation(a, a + m));
    cout << ans<<"\n";
}
int main()
{
    while (cin >> N >> m)
    {
        for (int i = 0; i < m; i++)
            cin >> a[i];
        if (N == 0 || m == 0)
            cout << "0\n";
        else
            gao();
    }
}

7.6320: levil的数组

数据量不大,我们可以先考虑一个暴力的做法。
我们直接从最小或者最大开始删点,当中间无法删了,说明接下来这个点也无法删去了。
因为对于它相邻的点,只可能删去变大,那么他们的差距只会变大。显然更不符合了。
所以小顶堆模拟这个删点的过程即可。
当然参赛选手也有死循环直接暴力的。

#include<bits/stdc++.h>
using namespace std;
#define dbg(ax) cout << "now this num is " << ax << endl;

typedef pair<int,int> pii;
int a[105],vis[105];
priority_queue<pii,vector<pii>,greater<pii> > Q;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;++i) 
    {
        scanf("%d",&a[i]);
        Q.push(pii{a[i],i});
    }
    while(Q.size() > 1)
    {
        int u = Q.top().second;
        int val = Q.top().first;
        Q.pop();
        vis[u] = 1;
        int le = u-1,ri = u+1;
        while(vis[le] && le > 0) le--;
        while(vis[ri] && ri <= n) ri++;
        if(le < 1 && ri > n) break; 
        if(le >= 1 && abs(val-a[le]) <= 1) continue;
        if(ri <= n && abs(val-a[ri]) <= 1) continue;
        break;
    }
    if(Q.size() > 1) printf("NO\n");
    else printf("YES\n");
    return 0;
}

对于每个位置,我们考虑是否能删去他。
那么,显然能删掉他的点,就是他左边第一个比他大的数,和右边第一个比他大的数。
所以我们只需要用单调栈来维护出每个点左边第一个大的数和右边第一个大的数即可。时间复杂度O(n),单调栈可以AC TZOJ4244: Sum

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 55;
int L[N], R[N], a[N];
pair<int, int> p[N];
int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]), p[i] = {a[i], i};
        L[i] = i - 1, R[i] = i + 1;
    }
    sort(p + 1, p + n + 1);
    for(int i = 1; i < n; i++) {
        int id = p[i].second;
        if(L[id] != 0 && abs(a[id] - a[L[id]]) <= 1 || R[id] != n + 1 && abs(a[id] - a[R[id]]) <= 1) {
            L[R[id]] = L[id], R[L[id]] = R[id];
        }
    }
    int id = p[n].second;
    printf("%s\n", L[id] == 0 && R[id] == n + 1 ? "YES" : "NO");
    return 0;
}

8.6322: levil的小岛

这个题目中的边权是2^i。
满足一个性质2^i> 2^(i-1)+ 2^(i-2) + 2^(i-3) + .. +2^0。
所以当我们越之前能走到这个点肯定比后面的边权要小。
那么,我们对应输入的顺序去维护一棵最小生成树。
然后我们再考虑去计算这个总边权。如果我们枚举出每条路径,来计算总值,显然是过于暴力的做法。
我们考虑去计算每条边的贡献,可以发现,每条边的贡献,就是dp[u] * (n-dp[u]) ;dp[u]表示u的子树里的点数量。
所以我们只需要树形dp统计出每个点的子树里的节点数量即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5+5;
const LL Mod = 1e9+7;
#define rg register
struct Node{int to;LL dis;};
vector<Node> G[N];
int n,m,fa[N],ssize[N];
int Find(int x){return x == fa[x] ? x : fa[x] = Find(fa[x]);}
LL ans = 0;
void dfs(int u,int fa,LL dis)
{
    ssize[u] = 1;
    for(auto v : G[u])
    {
        if(v.to == fa) continue;
        dfs(v.to,u,v.dis);
        ssize[u] += ssize[v.to];
    }
    if(u != 1)
    {
        LL num = n-ssize[u];
        ans = (ans+num*ssize[u]%Mod*dis%Mod)%Mod;
    }
}
int main()
{
    scanf("%d %d",&n,&m);
    for(rg int i = 1;i <= n;++i) fa[i] = i;
    LL pre = 1;
    while(m--)
    {
        pre = pre*2%Mod;
        int u,v;scanf("%d %d",&u,&v);
        if(u == v) continue;
        int xx = Find(u),yy = Find(v);
        if(xx != yy)
        {
            fa[xx] = yy;
            G[u].push_back(Node{v,pre});
            G[v].push_back(Node{u,pre});
        }
    }
    dfs(1,0,0);
    printf("%lld\n",ans); 
    return 0;
}

出题人还口胡了另外一个做法,你们可以尝试下。
首先,我们枚举出关于某个点的路径数,对路径进行排序。
如果要一条条去计算路径的长度,显然不行。
所以先思考树上点分治,在进行路径距离更新时,将单条路径算入答案,然后做一个
距离前缀和的计算,处理每条边被经过的次数。也许就能计算了

9.6192: Alice与拼凑钱币

这个题目就是背包路径,是一个SPJ的题目,你可以输出任何一种方案。
我们可以使用二维数组记录一下这个东西选之后的价值,然后倒推回来就可以了。

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=705,M=50005;

struct T
{
    int w,num;
    int id;
}a[N];
int cmp(T a,T b)
{
    return a.w/a.num<b.w/b.num;
}
bool s[N][M];
int dp[M],ans[205];
int main()
{
    int n,g;
    while(~scanf("%d%d",&n,&g))
    {
        int tot=0;
        for(int i=0,w,num,t;i<n;i++)
        {
            scanf("%d%d",&w,&num);
            if (num > g / w)
				num = g / w;
			t = 1;
            for(;t<=num;t<<=1)
			{
				a[tot].w=t*w;
                a[tot].num=t;
                a[tot++].id=i;
                num-=t;
            }
            if(num)
            {
                t=num;
                a[tot].w=t*w;
                a[tot].num=t;
                a[tot++].id=i;
            }
        }
        sort(a,a+tot,cmp);
        memset(dp, INF, sizeof(dp));
        memset(s,0,sizeof (s));
        memset(ans,0,sizeof (ans));
        dp[0]=0;
        for (int i = 0; i < tot; i++)
		{
			for (int j = g; j >= a[i].w; j--)
				if (dp[j] >= dp[j - a[i].w] + a[i].num)
					dp[j] = dp[j - a[i].w] + a[i].num, s[i][j] = 1;
		}
        printf("%d\n", dp[g]);
		int flag=0,tmp = g;
		for (int i = tot-1 ; i != -1; i--)
			if (s[i][tmp])
			{
				tmp -= a[i].w;
                ans[a[i].id]+=a[i].num;
			}
		for(int i=0;i<n;i++)
        {
            printf("%d%c",ans[i],i==n-1?'\n':' ');
        }
    }
    return 0;
}

10.6199: 异或序列

什么是异或呢,就是如果两个相应的二进制位相同,该位结果为0,否则为1。
那么两个相同的数字异或会发成什么,变成0,相同的数字就会被抵消。
而且题目有个(i+k) mod N,这个其实就是把这个序列变为之前的两倍长,最后一个也可以作为开始。
两两异或是0,那如果A数组两两异或,B数组两两异或呢,相当于x没异或,我们可以把它转化为字符串求解,也就是两段相等。[0,n)用来存储A数组两两异或值,因为我要求等于B的,所以应该把B复制两遍。以A异或后的值为模式串求位置。我们可以使用exkmp,找(2n,3n]中位置O最大前后缀长度为n,也就nex数组的值为n,A要和B相等,比如到3n匹配了,相当于A没有移动,也就是k是0,a[0]^b[0]就是答案;到3n-1匹配了,相当于A往后移动1个,也就是k是1,a[1]^b[0]就是答案

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int a[N], b[N];
int p[N * 3], nex[N * 3];
int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    for (int i = 0; i < n; i++)
        cin >> b[i];
    for (int i = 0; i < n; i++)
    {
        p[i] = (a[i] ^ a[(i + 1) % n]);
        p[i + n] = p[i + n + n] = (b[i] ^ b[(i + 1) % n]);
    }
    int m = 3 * n;
    nex[0] = nex[1] = 0;
    p[n]=-1;
    for (int i = 1, j; i < m; i++)
    {
        j = nex[i];
        while (j && p[i] != p[j])
            j = nex[j];
        nex[i + 1] = p[i] == p[j] ? j + 1 : 0;
    }
    for (int k = 0; k < n; k++)
    {
        if (nex[m - k] == n)
        {
            cout << k << " " << (a[k] ^ b[0]) << "\n";
        }
    }
    return 0;
}

11.6200: 图的最小生成树

这个题竟然被AC了,很强。
我们可以先用kruskal求出最小生成树,然后以顶点1建立有根树。然后我们可以用LCA去查询结果。之后用DP去记录下孩子数。什么时候不需要变动呢,就是最小生成树的时候已经选择这条边了,也就是他们两在这棵树中已是父子关系。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
const int N = 1e5 + 5;
int fa[N], n, m;
pair<int, pair<int, int>> V[N], V1[N];
vector<pair<int, int>> G[N];
pair<int, int> valToFa[N][20];
int p[N][20];
int son[N], deep[N];
int Find(int x)
{
    if (x == fa[x])
        return x;
    return fa[x] = Find(fa[x]);
}
void Union(int x, int y)
{
    x = Find(x);
    y = Find(y);
    if (x != y)
        fa[x] = y;
}
void Dfs(int u, int f)
{
    for (auto X : G[u])
    {
        int v = X.fi, w = X.se;
        if (v == f)
            continue;
        fa[v] = u;
        deep[v] = deep[u] + 1;
        valToFa[v][0] = {w, v};
        Dfs(v, u);
        son[u] += son[v];
    }
    son[u]++;
}
void InitLca()
{
    Dfs(1, 0),fa[1]=0;
    for (int i = 1; i <= n; i++)
        p[i][0] = fa[i];
    for (int j = 1; j < 20; j++)
    {
        for (int i = 1; i <= n; i++)
        {
            p[i][j] = p[p[i][j - 1]][j - 1];
            valToFa[i][j] = max(valToFa[i][j - 1], valToFa[p[i][j - 1]][j - 1]);
        }
    }
}
int Lca(int u, int v)
{
    if (deep[u] > deep[v])
        swap(u, v);
    for (int i = 19; i >= 0; i--)
    {
        if (deep[v] - deep[u] >= (1 << i))
            v = p[v][i];
    }
    if (u == v)
        return u;
    for (int i = 19; i >= 0; i--)
    {
        if (p[u][i] != p[v][i])
            u = p[u][i], v = p[v][i];
    }
    return p[u][0];
}

int main()
{
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        fa[i] = i;
    for (int i = 0, u, v, w; i < m; i++)
    {
        cin >> u >> v >> w;
        V[i] = V1[i] = {w, {u, v}};
    }
    sort(V, V + m);
    long long ans = 0;
    for (int i = 0, u, v, w; i < m; i++)
    {
        u = V[i].se.fi, v = V[i].se.se, w = V[i].fi;
        if (Find(u) != Find(v))
        {
            ans += w;
            Union(u, v);
            G[u].push_back({v, w});
            G[v].push_back({u, w});
        }
    }
    InitLca();
    for (int i = 0, u, v, w; i < m; i++)
    {
        u = V1[i].se.fi, v = V1[i].se.se, w = V1[i].fi;
        if (fa[u] == v || fa[v] == u)
        {
            cout << ans << " " << 0 << "\n";
            continue;
        }
        int t = Lca(u, v);
        pair<int, int> res = {0, 0};
        for (int i = 19; i >= 0; i--)
        {
            if (deep[u] - deep[t] >= (1 << i))
            {
                res = max(valToFa[u][i], res);
                u = p[u][i];
            }
            if (deep[v] - deep[t] >= (1 << i))
            {
                res = max(valToFa[v][i], res);
                v = p[v][i];
            }
        }
        cout << ans + w - res.fi << " " << son[res.se] << "\n";
    }
}
posted @ 2020-08-30 19:47  暴力都不会的蒟蒻  阅读(589)  评论(0编辑  收藏  举报