2020年“感恩杯”台州学院第十三届大学生程序设计竞赛题解

题目考察范围及做法一览
6445: 小z游东湖 if或ceil函数
6455: 学学学 状压DP
6447: 小z的零花钱 一重循环
6470: 小z与他的袜子 思维找循环节or数组模拟状态
6466: 小z买文具 一重循环
6467: 小z的家庭作业 简单数组
6449: 小z与直角三角形 两重循环
6469: 小z的遥控车 搜索
6468: 小z的班级平时分 多维数组+字符串比较
6451: levil与因子和 数论
6454: 乐呵采蘑菇 贪心
6450: levil与时间点 STL
6471: 小z的迷宫游戏 广搜或tarjan或者处理过的拓扑排序
简单题:小z游东湖、小z的零花钱 、小z买文具、小z与直角三角形、小z的家庭作业、小z的班级平时分(不太准)
中等题:小z与他的袜子(不太准)、小z的遥控车(不太准)、乐呵采蘑菇、levil与时间点
难题:levil与因子和、小z的迷宫游戏、学学学

1.6445: 小z游东湖

选择大船需要的条数少,直接/6得到的是商不包含余数,可以判断一下是否有余数也可以用ceil进行向上取整

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

int main()
{
    int x;
    cin>>x;
    cout<<(int)ceil(x/6.0);
}

也可以直接把它+5再➗6,这样多1~5个人都会多一条船,恰好够不多船

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int a;
    cin>>a;
    cout<<(a+5)/6<<"\n";
    return 0;
}

2.6455: 学学学

这类数据量这么小的往往都可以使用状压DP去实现
儿子排好序后父节点才会访问,我们定义二进制上为1代表这个点已经访问过,即参与过排序
父节点均可以由子节点转移而来,我们可以用son来表示转移的合法状态,用dp进行统计,二进制全为1即所有算法都学了
这个过程中属于加法原理

#include<stdio.h>
#include<string.h>
using namespace std;
#define ll long long int
int n,m;
ll dp[(1<<19)];
int son[19];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        x--;y--;
        son[x]|=(1<<y);
    }
    int end=(1<<n);
    dp[0]=1;
    for(int i=0;i<end;i++)
    {
        if(dp[i]>0)
        {
            for(int j=0;j<n;j++)
            {
                if((i&(son[j]))==son[j])
                {
                    if((i&(1<<j))==0)
                    {
                        dp[(i|(1<<j))]+=dp[i];
                    }
                }
            }
        }
    }
    printf("%I64d\n",dp[end-1]);
    return 0;
}

3.6447: 小z的零花钱

我们可以一重循环模拟下他的零花钱,设置两个变量,1个为今天拿到的钱,还有总钱,比较简单。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,k;
    cin>>n>>k;
    int sum=0,t=0;
    for(int i=0;i<n;i++)
    {
        if(i%k==0)t++;
        if(t>5)t=5;
        sum+=t;
    }
    cout<<sum<<"\n";
    return 0;
}

4.6470: 小z与他的袜子

这个是存在循环的,但是思考起来比较麻烦。我们可以考虑数组的办法。
k天,k次循环,早上找到编号最小的没穿的,也就是数组为0的拿来穿;
穿完给他标记下,当前元素变为1;
晚上看看是不是有n-1双袜子了,是的话给他洗了,给n-1双变为2;
什么时候收袜子呢,肯定要在洗之前,如果已经是2,代表昨天洗了,把它们收进来重新置0即可。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int n,k;
    int a[1005]={0};
    cin>>n>>k;
    int ans;
    for(int i=0;i<k;i++)
    {
        int flag;
        for(int j=1;j<=n;j++)
        {
            if(a[j]==0)
            {
                flag=j;
                break;
            }
        }
        ans=flag;
        a[flag]=1;
        for(int j=1;j<=n;j++)
        {
            if(a[j]==2)a[j]=0;
        }
        int t=0;
        for(int j=1;j<=n;j++)
        {
            if(a[j])t++;
        }
        if(t==n-1)
        {
            for(int j=1;j<=n;j++)
            {
                if(a[j])a[j]=2;
            }
        }
    }
    cout<<ans<<"\n";
}

也可以直接找到循环的规律

\[\underbrace{1, 2, \cdots, n}_{n\text{ numbers}},\underbrace{1, 2, \cdots, n - 1}_{n - 1\text{ numbers}},\underbrace{1, 2, \cdots, n - 2, n}_{n - 1\text{ numbers}},\underbrace{1, 2, \cdots, n - 1}_{n - 1\text{ numbers}},\underbrace{1, 2, \cdots, n - 2, n}_{n - 1\text{ numbers}},\cdots \]

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

int main()
{
    int n, k, ans;
    cin >> n >> k;
    if (k <= n)
    {
        ans = k;
    }
    else
    {
        k -= n;
        if (k % (n - 1))
            ans = k % (n - 1);
        else if ((k / (n - 1)) % 2)
            ans = n - 1;
        else
            ans = n;
    }
    cout << ans;
    return 0;
}

5.6466: 小z买文具

需要执行n次循环统计一下总钱数,然后再买买东西的花费减去

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

int main()
{
    int n,m;
    cin>>n>>m;
    double sum=0;
    for(int i=0;i<n;i++)
    {
        int x;
        cin>>x;
        sum+=x;
    }
    for(int i=0;i<m;i++)
    {
        double w;
        int y;
        cin>>w>>y;
        sum-=w*y;
    }
    if(sum==0)
    {
        cout<<"Perfect\n";
    }
    else if(sum<0)
    {
        cout<<"No";
        printf(" %.1f",-sum);
    }
    else
    {
        cout<<"Yes";
        printf(" %.1f",sum);
    }
    return 0;
}

6.6467: 小z的家庭作业

由于不知道老师的参考时间,所以作业必定需要存储下来,等到知道参考时间再统计,注意等于也属于超时

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

int main()
{
    int n;
    int a[55];
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }
    int sum=0;
    double s;
    cin>>s;
    for(int i=0;i<n;i++)
    if(a[i]>=s)sum++;
    cout<<sum<<"\n";
    return 0;
}

7.6449: 小z与直角三角形

可以让第一重循环从1到z,较小者为勾,那么股需要大于勾,股可以从勾+1到z,如果等于z输出即可。
不存在可以用一个旗子标记下

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

8.6469: 小z的遥控车

这题正解为BFS,因为广搜的时候可以保证步数最少,直接搜到x就可以了

#include <bits/stdc++.h>
using namespace std;
int dir[6]={1,-1,7,-7,10,-10};
int s, e;

int visited[300], step[300];
int BFS()
{
	queue<int> qu;
	qu.push(s);
	visited[150+s] = 1;
	step[150+s] = 0;
	while(!qu.empty())
	{
		int cur = qu.front();
		qu.pop();
		if(cur==e)
			return step[150+cur];
		for(int i=0;i<6;i++)
		{
			int nxt = cur+dir[i];
			if(abs(nxt)<=120 && visited[150+nxt]==0)
			{
				visited[150+nxt] = 1;
				step[150+nxt] = step[150+cur]+1;
				qu.push(nxt); 
			}
		}
	}
} 
int main()
{
	int cas;
	cin>>cas;
	while(cas--)
	{
		memset(visited, 0, sizeof(visited));
		memset(step, 0, sizeof(step));
		s = 0;
		cin>>e;
		cout<<BFS()<<endl;
	}
	return 0;
} 

我们可以深搜记录下最小的步数

#include <bits/stdc++.h>
using namespace std;
int n, k ,t;
void dfs(int x, int s)
{
    if (s >= t)
        return;
    if (x == n)
    {
        t = min(t, s);
        return;
    }
    if (x > n)
    {
        dfs(x - 1, s + 1);
        dfs(x - 10, s + 1);
        dfs(x - 7, s + 1);
    }
    else
    {
        dfs(x + 1, s + 1);
        dfs(x + 10, s + 1);
        dfs(x + 7, s + 1);
    }
}
int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        t = 123456;
        cin >> n;
        dfs(0, 0);
        cout << t << "\n";
    }
    return 0;
}

先贪心处理一个区间,然后直接加

#include <iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
float s[50];
int main()
{
    int n,jl,tot,i;
    cin>>jl;
    for(i=1;i<=jl;i++)
    {
        tot=0;
        cin>>n;if(n<0)n=-n;
        if(!(n%10)){tot=n/10;n=0;}
        if(n>20){tot+=(n-10)/10;n=(n-10)%10+10;}
        while(n)
        {
            if(!(n%7)){tot+=n/7;n=0;}
            else
            {
                if(n==19)tot+=3;
                if(n==18)tot+=3;
                if(n==17)tot+=2;
                if(n==16)tot+=3;
                if(n==15)tot+=3;
                if(n==13)tot+=3;
                if(n==12)tot+=3;
                if(n==11)tot+=2;
                if(n==9)tot+=2;
                if(n==8)tot+=2;
                if(n==6)tot+=2;
                if(n==5)tot+=3;
                if(n==4)tot+=3;
                if(n==3)tot+=2;
                if(n==2)tot+=2;
                if(n==1)tot+=1;
                n=0;
            }
        }
        cout<<tot<<endl;
    }
    return 0;
}

杨添盛自己动手找到了答案的表

#include <bits/stdc++.h>
using namespace std;
int main()
{	
	int a[102]={1,	2,	3,	3,	3,	2,	1,	2,	2,	1,
				2,	3,	3,	2,	3,	3,	2,	3,	3,	2,
				3,	4,	4,	3,	4,	4,	3,	4,	4,	3,
				4,	5,	5,	4,	5,	5,	4,	5,	5,	4,
				5,	6,	6,	5,	6,	6,	5,	6,	6,	5,
				6,	7,	7,	6,	7,	7,	6,	7,	7,	6,
				7,	8,	8,	7,	8,	8,	7,	8,	8,	7,
				8,	9,	9,	8,	9,	9,	8,	9,	9,	8,
				9,	10,	10,	9,	10,	10,	9,	10,	10,	9,
				10,	11,	11,	10,	11,	11,	10,	11,	11,	10
				};
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		cin>>n;
		n=abs(n);
		if(n==0) cout<<0<<"\n";
		else cout<<a[n-1]<<"\n";
	}
	return 0;
}

9.6468: 小z的班级平时分

这个题是比较简单,我们二维数组存储下,然后就是找最大值和最小值,我们记录下下标就可以,代码是有点长,但是写起来还是容易的
原标程有bug,现已更换。感谢Ycp

#include <bits/stdc++.h>
using namespace std;
char s[15][15][25];
int a[15][15];
int main()
{
    int n,m;
    while(cin>>n>>m)
    {
        int mini=1,minj=1;
        int maxi=1,maxj=1;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                cin>>s[i][j];
                if(strcmp(s[i][j],"None"))
                {
                    mini=i,minj=j;
                    maxi=i,maxj=j;
                }
            } 
        }
        memset(a,0,sizeof(a));
        int q;
        cin>>q;
        while(q--)
        {
            int op,x,y;
            cin>>op>>x>>y;
            if(op==1)
            {
                a[x][y]-=5;
            }
            else
            {
                a[x][y]+=3;
            }
        }

        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(strcmp(s[i][j],"None"))
                {
                    if(a[i][j]<a[mini][minj])
                    {
                        mini=i;
                        minj=j;
                    }
                    if(a[i][j]==a[mini][minj]&&strcmp(s[i][j],s[mini][minj])<0)
                    {
                        mini=i;
                        minj=j;
                    }
                    if(a[i][j]>a[maxi][maxj])
                    {
                        maxi=i;
                        maxj=j;
                    }
                    if(a[i][j]==a[maxi][maxj]&&strcmp(s[i][j],s[maxi][maxj])<0)
                    {
                        maxi=i;
                        maxj=j;
                    }
                }
            }
        }
        cout<<s[maxi][maxj]<<" "<<a[maxi][maxj]<<"\n";
        cout<<s[mini][minj]<<" "<<a[mini][minj]<<"\n";
    }
    return 0;
}

10.6451: levil与因子和

考虑每个数的倍数会在这个区间中出现几个,然后再乘上每个数即可(也就是一个个计算每个数的贡献)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int main()
{
    int a,b;scanf("%d %d",&a,&b);
    LL ans = b - a + 1;
    for(int i = 2;i <= b;++i)
    {
        ans += (LL)(i * (b / i - (a - 1) / i));
        if(i >= a && i <= b) ans -= i;
    }
    printf("%lld",ans);
    return 0;
}

如果给你1~n的因子和会做吗,其实就是整数分块,然后最后用一个等差数列处理下

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll la(int n)
{
    ll sum = 0;
    for (int i = 1; i <= n; ++i)
    {
        sum += n / i * i;
    }
    if (n)
    {
        sum -= (2 + n) * 1LL * (n - 1) / 2;
    }
    return sum;
}
int main()
{
    int a, b;
    scanf("%d%d", &a, &b);
    cout << la(b) - la(a - 1) << "\n";
    return 0;
}

11.6454: 乐呵采蘑菇

这个题目和渊子赛马类似,我们只需要让最不浪费的人去拿蘑菇就好
也就是枚举小水潭从小开始,如果手臂半径大于小水潭那么就拿到,这样我们就可以确保以最小的去拿到,直接采摘人全部没有。

#include <bits/stdc++.h>
using namespace std;
#define N 20001
int a[N],b[N];
int main()
{
    int n,m;
    ios::sync_with_stdio(false);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=0;i<n;i++)scanf("%d",&a[i]);
        for(int i=0;i<m;i++)scanf("%d",&b[i]);
        sort(a,a+n);
        sort(b,b+m);
        int i,res=0,j=0;
        for(i=0;i<m;i++)
        {
            if(a[j]<=b[i])
            {
                res+=b[i];
                j++;
                if(j==n)break;
            }
        }
        if(j<n)puts("Sad!");
        else printf("%d\n",res);
    }
}

12.6450: levil与时间点

操作包含删除和插入,这种往往需要使用数据结构,STL的map是满足这个特性的,也可以使用pq记录下最大的,速度还是蛮快的
T*sqrt(a[i])会超时,所以要先离散化,过了的是水过去了
出题人的代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define dbg(ax) cout << "now this num is " << ax << endl;
const int N = 1e5 + 5;
priority_queue<int,vector<int>,less<int> > Q;//小顶堆
map<int,int> mp,yz;//map计数
struct Query{int id,y;}p[N];
int a[1005],tot = 0;
int main()
{
    int T,x;scanf("%d %d",&T,&x);
    Q.push(x),mp[x]++;
    a[++tot] = x;
    for(int i = 1;i <= T;++i)
    {
        scanf("%d",&p[i].id);
        if(p[i].id != 1) scanf("%d",&p[i].y);
        if(p[i].id == 2) a[++tot] = p[i].y;
    }
    sort(a + 1,a + tot + 1);
    int len = unique(a + 1,a + tot + 1) - a - 1;
    for(int i = 1;i <= len;++i)
    {
        if(a[i] == 1 || a[i] == 2 || a[i] == 3) yz[a[i]] = 1;
        else
        {
            int m = sqrt(a[i]),ans = 1;
            for(int j = 2;j <= m;++j) if(a[i] % j == 0) ans = max(ans,max(j,a[i] / j));
            yz[a[i]] = ans;
        }
    }
    for(int i = 1;i <= T;++i)
    {
        if(p[i].id == 1)
        {
            while(mp[Q.top()] == 0) Q.pop();
            int mx = Q.top();
            printf("%d\n",yz[mx]);
        }
        else
        {
            int z = p[i].y;
            if(p[i].id == 2) Q.push(z),mp[z]++;
            if(p[i].id == 3) printf("%d\n",mp[z]);
            if(p[i].id == 4) mp[z]--;
        }
    }
    return 0;
}

被我水过去了

#include <bits/stdc++.h>
using namespace std;
priority_queue<int> pq;
map<int, int> M;
int la(int n)
{
    int x=sqrt(n+0.5);
    for(int i=2;i<=x;i++)
    {
        if(n%i==0)return n/i;
    }
    return 1;
}
int main()
{
    int T, x;
    cin >> T >> x;
    pq.push(x), M[x]++;
    while (T--)
    {
        int op;
        cin >> op;
        if (op == 1)cout<<la(pq.top())<<"\n";
        else if(op==2)
        {
            cin>>x;
            pq.push(x), M[x]++;
        }
        else if(op==3)
        {
            cin>>x;
            if(M.count(x))
            {
                cout<<M[x]<<"\n";
            }
            else
            {
                cout<<"0\n";
            }
        }
        else
        {
            cin>>x;
            if(M.count(x))
            {
                M[x]--;
                if(M[x]==0)
                {
                    M.erase(x);
                }
            }
        }
    }
    return 0;
}

13.6471: 小z的迷宫游戏

有环,而且i只会有一个Fi,那么tarjan求环即强连通分量非常不错啊

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
struct graph
{
    int head[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn<<1],sz;
    void init(){memset(head,-1,sizeof(head));}
    graph(){init();}
    void push(int a,int b,int c){nxt[sz]=head[a],to[sz]=b,w[sz]=c,head[a]=sz++;}
    int& operator[](const int a){return to[a];}
}g;
int a[maxn],dp[maxn],dfn[maxn],low[maxn],tot,cnt;
int scc[maxn],sum[maxn];
bool instk[maxn];
bool circle[maxn];
stack<int>stk;
void tarjan(int now)
{
    low[now]=dfn[now]=++tot;
    stk.push(now);
    instk[now]=1;
    for(int i = g.head[now];~i;i = g.nxt[i]){
        if(!dfn[g[i]]){
            tarjan(g[i]);
            low[now]=min(low[now],low[g[i]]);
        }
        else if(instk[g[i]]){
            low[now]=min(low[now],dfn[g[i]]);
        }
    }
    if(low[now]==dfn[now]){
        int u,c;
        c=0;
        cnt++;
        do{
            c++;
            u = stk.top(),stk.pop();
            scc[u]=cnt,sum[cnt]+=a[u];
            instk[u]=0;
        }while(u!=now);
        if(c>1){
            circle[cnt]=1;
        }
    }
}
int dfs(int x)
{
    int now = scc[x];
    if(circle[now])return sum[now];
    if(dp[now])return dp[now];
    dp[now]=sum[now];
    for(int i = g.head[x];~i;i = g.nxt[i]){
        dp[now]+=dfs(g[i]);
    }
    return dp[now];
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;++i)scanf("%d",&a[i]);
    for(int i = 1,b;i <= n;++i){
        scanf("%d",&b);
        if(b!=i)
        g.push(i,b,0);
    }
    for(int i = 1;i <= n;++i){
        if(!dfn[i])tarjan(i);
    }
    for(int i = 1;i <= n;++i){
        printf("%d\n",dfs(i));
    }
    return 0;
}

同一关卡刷多次不算分,但是这个图仅仅会是多个环带上几个链,所以不会走两次就能多刷一关,所以每个点仅访问一次
我们可以拓扑排序,把它放进栈里,因为栈先进后出,后进先出,我们可以得到后来的F[i]的答案再去更新i的答案

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
queue<int> q;
stack<int> S;
int n;
int d[N], f[N], e[N], ans[N];
bool vis[N];

void Top_sort()
{
    for (int i = 1; i <= n; i++)
        if (!d[i])
            q.push(i);
    while (!q.empty())
    {
        int t = q.front();
        q.pop();
        S.push(t);
        d[f[t]]--;
        if (!d[f[t]])
            q.push(f[t]);
    }
}
void dfs(int u, int root)
{
    if (vis[u])
        return;
    vis[u] = 1;
    ans[root] += e[u];
    q.push(u);
    dfs(f[u], root);
}
void circle()
{
    for (int i = 1; i <= n; i++)
        if (d[i] && !vis[i])
        {
            dfs(i, i);
            while (!q.empty())
            {
                int t = q.front();
                q.pop();
                ans[t] = ans[i];
            }
        }
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &e[i]);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &f[i]);
        d[f[i]]++;
    }
    Top_sort();
    circle();
    while (!S.empty())
    {
        int t = S.top();
        S.pop();
        ans[t] = e[t] + ans[f[t]];
    }
    for (int i = 1; i <= n; i++)
        printf("%d\n", ans[i]);
    return 0;
}

环上的时候就搜一下,做一下前缀和。DFS的话这个点太多会爆栈,可以使用BFS进行搜索

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
const int N = 2e5 + 5;
namespace FASTIO{
    inline LL read(){
        LL x = 0,f = 1;char c = getchar();
        while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();}
        while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();}
        return x*f;
    }
}
using namespace FASTIO;

int n,a[N],fa[N];
LL ans[N][2];
bool vis[N];
vector<int> vec;
void solve(int st)
{
    int pre = st;
    vis[st] = 1;
    ans[st][0] = a[st];
    vec.push_back(st);
    while(1)
    {
        int u = fa[pre];
        LL ma = ans[pre][0] + a[u];
        pre = u;
        if(vis[u])
        {
            if(ans[u][0] == -1)
            {
                for(auto v : vec) ans[v][1] = ma - ans[v][0] + a[v] - a[u] + ans[u][1];
            }
            else
            {
                ans[u][1] = ma - ans[u][0];
                for(auto v : vec)
                {
                    if(v == u) continue;
                    if(ans[v][0] < ans[u][0]) ans[v][1] = ans[u][0] - ans[v][0] - a[u] + a[v] + ans[u][1];
                    else ans[v][1] = ans[u][1];
                }
            }
            for(auto v : vec) ans[v][0] = -1;
            vec.clear();
            return ;
        }
        vis[u] = 1;
        vec.push_back(u);
        ans[u][0] = ma;
    }
}
int main()
{
    n = read();
    for(int i = 1;i <= n;++i) a[i] = read(),ans[i][0] = -1;
    for(int i = 1;i <= n;++i) fa[i] = read();
    for(int i = 1;i <= n;++i)
    {
        if(!vis[i]) solve(i);
    }
    for(int i = 1;i <= n;++i) printf("%lld\n",ans[i][1]);
    return 0;
}
posted @ 2020-12-19 12:02  暴力都不会的蒟蒻  阅读(1205)  评论(0编辑  收藏  举报