HNUCM信息科学与工程学院第五届大学生程序设计竞赛——正式赛

HNUCM信息科学与工程学院第五届大学生程序设计竞赛——正式赛

A

签到题

#include<iostream>
using namespace std;
int main(){
    int n;
    cin>>n;
    int x,ans=0;
    for(int i=0;i<n;i++){
        cin>>x;
        if(x>0)
        ans+=x*x;
        else
        ans+=-x;
    }
    cout<<ans<<'\n';
    return 0;
}

B

简单dp,取前面第五天的3倍就行

#include<iostream>
using namespace std;
int main(){
    int n;
    cin>>n;
    long long dp[210]={0,1,1,1,1};
    for(int i=5;i<=n;i++)
    dp[i]=dp[i-5]*3;
    cout<<dp[n]<<'\n';
    return 0;
}

C

这题被封了不记得什么题了

#include<iostream>
#include<algorithm>
using namespace std;
int main(){
    int n,m;
    cin>>n;
    int a[110];
    for(int i=1;i<=n;i++)
    cin>>a[i];
    sort(a+1,a+1+n);
    cin>>m;
    m*=12;
    int ans=0;
    for(int i=1;i<=n;i++){
        if(m-a[i]>=0)
        ans++;
        else
        break;
    }
    cout<<ans<<'\n';
    return 0;
}

D

枚举然后判断回文就行了

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
int t[1010];
int main(){
  string s;
  int a,b;
  cin>>a>>b;
    int l=1010,r=0;
  for(int i=1;i<=1000;i++){
  t[i]=i*i*i;
  if(t[i]>=a)
  l=min(i,l);
  if(t[i]<=b)
  r=max(r,i);
  }
  int ans=0;
  for(int i=l;i<=r;i++){
      s=to_string(t[i]);
      bool flag=true;
      for(int i=0;i<s.size()/2;i++)
      if(s[i]!=s[s.size()-i-1]){
          flag=false;
      break;
      }
      ans+=flag;
  }
  cout<<ans<<'\n';
    return 0;
}

E

简单dp,不能跳的位置置0

#include<iostream>
using namespace std;
int dp[100];
int main(){
    int x,y,n;
    cin>>x>>y>>n;
    dp[0]=dp[1]=1;
    for(int i=2;i<=n;i++){
        if(i==x||i==y)
        continue;
        dp[i]=dp[i-1]+dp[i-2];
    }
    cout<<dp[n];
    return 0;
}

F

算是一个简单思维题吧,先考虑偶奇依次排列,然后发现可能会剩下偶数或者奇数。

如果剩下的是偶数,因为偶数不会影响前面的奇偶性,所以在末尾首先加个偶数让长度+1,接着其他的随便放到当前的排列中就行了

如果是奇数,会发现每三个奇数可以构成让序列长度+2,也就是(奇数+奇数,奇数),再分别考虑余数,余数是1的话,只能让原序列最后的一个奇数和偶数与剩下的这个合在一起构成一个偶数,序列长度减一,余数是2,那么两个奇数凑成一个偶数加到序列末尾,长度+1

#include<iostream>
using namespace std;
int main(){
    int n,ans=0;
    cin>>n;
   int a=0,b=0,x;
    for(int i=0;i<n;i++){
        cin>>x;
        if(x&1)
        b++;
        else
        a++;
    }
    ans=min(a,b)*2;
    a-=ans/2;
    b-=ans/2;
    if(b>0){
        ans+=b/3*2;
        b%=3;
        if(b==1)
        ans--;
        else if(b==2)
        ans++;
    }
    else if(a>0)
    ans++;
    cout<<ans<<'\n';
    return 0;
}

G

怎么会有人把小月的天数打成29然后wa3发的,注意题目说了不会给闰年,所以压根不要判断闰年,比赛脑抽写了一个先判断闰年再计算天数的代码,要不是最后因为小月30天wa了,都没看到这个条件

#include<iostream>
using namespace std;
bool judge(int n){
    if(n%4==0&&n%100!=0||n%400==0)
    return true;
    return false;
}
int day[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int main(){
    int a,b,c,t;
    cin>>t;
    while(t--){
    cin>>a>>b>>c;
    c++;
        if(c>day[b])
        b++,c=1;
        if(b>12)
        a++,b=1;
    cout<<a<<'-';
    if(b<10)
    cout<<'0';
    cout<<b<<'-';
    if(c<10)
    cout<<0;
    cout<<c<<'\n';
}
    return 0;
}

H

这个题考场上思路基本都出来了,但是写起来有点恼火,就没写了,看了学长的题解,个人感觉有些问题,情况考虑少了,学长是dfs+全排列+逆元写的。还在和学长讨论中,这里大概讲一下我的思路。

对于每个数,只考虑它模5的结果,也就是0,1,2,3,4,如果想要连续三个数和是5的倍数,那么最开始三个数无非就是5种情况:

  1. 1 1 3
  2. 2 4 4
  3. 0 3 2
  4. 0 4 1
  5. 0 0 0

其中对于每个情况来说,只要前三个数的顺序确定,后面的所有数的顺序都应该是确定了的,比如第一种情况,假设是1 1 3这个排列,后面的数一定是按照1 1 3 1 1 3 1…这个顺序排列下去的。

我们先不考虑全排列的去重问题

接下来再分类考虑下,可以把1 2归位一类,他们序列中余数种类有2种,同理3,4分为一类,5单独成一类

  • 对于情况5而言,只需要判断是否只有一种余数且余数是0即可,那么方案数就是n*(n-1) *(n-2)…1
  • 现在考虑1,2这两种情况,1和2考虑类似,拿1,1,3来说,假设n个数中余数为1个数为a,为3的个数为b,y=n%3,x=n/3。
    • y=0,很显然,如果a=2*x,b=x是存在序列的。选取的方案数为2x (2x-1)(2x-2)…(1)(x)(x-1)…1,注意这个不是结果,因为3 1 1之间还可以进行排列,所以还要乘以个3
    • y=1,先考虑是否存在解,判定条件就是a≥2x&&b≥x是否成立,在考虑剩下的数是1还是3,如果是1的话它前面的两个数可以是1 3也可以是3 1,也就是存在两种余数的排列方案,接着再考虑下确定余数排列后选取方案,将它乘以2即可,如果剩下的是3,那么前面两个数只能是1 1,答案也就是确定余数排列后的选取方案数
    • y=2,当且仅当1和3都只剩一个的时候才行,类似的1和3可以排列为 1 3和 3 1,从而最后算出选取方案数后乘以2就可以了
  • 也可以根据上面的思路进行考虑,不过n%3结果的处理以及选取方案数的计算略有不同,自行考虑

现在还没完,我们还要考虑去重的问题,如果n个数中两个数相同,那么他们是等价的,注意不是余数相同,那么对于每种数而言,假设他在序列中出现了num次,我们只需要把答案除以num!就可以了。需要注意的是这里面因为数很大,所以时时刻刻要进行取模操作,因此这一步进行除法的时候我们需要用逆元处理一下

下面给一份我写的也许是对的代码,一个是因为学校oj现在交不了这题,一个是因为不知道std是不是有问题

#include <iostream>
#include<vector>
#include<map>
#include<string>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
const ll mod = 1e9 + 7;
ll exgcd(ll a, ll b, ll& x, ll& y) {
	if (b == 0) {
		x = 1, y = 0;
		return a;
	}
	ll d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
ll inverse(ll a, ll m) {
	ll x, y;
	exgcd(a, m, x, y);
	return (x % m + m) % m;
}
void solve()
{
	int n, x;
	cin >> n;
	map<int, int>mp,num;
	for (int i = 1; i <= n; i++) {
		cin >> x;
		mp[x%5]++;
		num[x]++;
	}
	ll c = 1;
	for (auto it : num) {
		for (int i = 1; i <= it.second; i++)
			c = c * i % mod;
	}
	c = inverse(c, mod);//去重时除数在模mod意义下的逆元
	if (mp.size() >= 4) {
		cout << 0 << '\n';
		return;
	}//不存在这种方案
	if (mp.size() == 1) {
		ll ans = 1;
		if (mp.begin()->first == 0)//0 0 0.......
			for (int i = 1; i <= n; i++)
				ans = ans * i % mod;
		else
			ans = 0;
		cout << ans*c%mod << '\n';
	}
		vector<pair<int, int> >a;
		for (auto it : mp)
			a.push_back(it);
		if (a.size() == 2) {
			ll ans=0;
			if (a[0].first == 2 && a[1].first == 4|| a[0].first == 1 && a[1].first == 3) {
				int x = n / 3,y=n%3;
				if (a[0].first == 1)
					swap(a[0].second, a[1].second);//2 4 4 或者3 1 1
				a[1].second -= x * 2, a[0].second -= x;
				if (a[1].second >= 0 && a[0].second >= 0) {
					ans = c;
					for (int i = 2*x+a[1].second; i >a[1].second; i--)
						ans = ans * i % mod;
					for (int i = x+a[0].second; i >a[0].second; i--)
						ans = ans * i % mod;
					if (y == 0) {
						ans = ans * 3 % mod;
					}
					else if (y == 1) {
						if (a[0].second != 1 && a[1].second != 1)
							ans = 0;
					}
					else {
						if (a[0].second == 1 && a[1].second == 1)
							ans = 2 * ans % mod;
						else if (a[0].second == 2)
							ans = 0;
						else
							ans = ans * 2 % mod;
					}
				}
					
			}
			cout << ans << "\n";
		}
		else if(a[0].first==0&&(a[1].first==1&&a[2].first==4||a[1].first==2&&a[2].first==3)) {//0 1 4或者0 2 3
			int x = n / 3, y = n % 3;
			ll ans = 0;
			a[0].second -= x, a[1].second -= x, a[2].second -= x;
			if (a[0].second >= 0 && a[1].second >= 0 && a[2].second >= 0) {
				ans = c;
				for (int i = x + a[2].second; i > a[2].second; i--)
					ans = ans * i % mod;
				for (int i = x + a[1].second; i > a[1].second; i--)
					ans = ans * i % mod;
				for (int i = x + a[0].second; i > a[0].second; i--)
					ans = ans * i % mod;
				if (y == 0) {
					ans = ans * 6 % mod;
				}
				else if (y == 1)
					ans = ans * 2 % mod;
				else if (a[0].second == 2 || a[1].second == 2 || a[2].second == 2)
					ans = 0;
				else
					ans = ans * 2 % mod;
			}
			cout << ans << '\n';
		}
}

int  main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T = 1;
	//	cin >> T;
	while (T--)
	{
		solve();
	}
	return 0;
}

I

这题当时写的时候忘记判断最后左右括号是否完全抵消了,纯纯脑抽wa一发,用栈模拟或者直接计算右括号是否始终大于等于左括号

#include<iostream>
#include<string>
#include<stack>
using namespace std;
int main(){
   string s;
   int t;
  while( cin>>t){
   while(t--){
       cin>>s;
       int a=0,b=0;
       bool flag=true;
       for(int i=0;i<s.size();i++){
           if(s[i]=='(')
           a++;
           else
           b++;
           if(b>a)
           {
               flag=false;
               break;
           }
       }
       if(flag&&a==b)
       cout<<"True"<<'\n';
       else
       cout<<"False"<<'\n';
   }
  }
    return 0;
}

J

这个题一开始我就注意到了行列,但是属实给我wa到emo了,因为刚开始看错了数据范围,n和m都看成10000了,所以在用排列组合+逆元写,按道理应该是能过的,但是后面改了数据后还是一直91%,到快结束才发现,n和m才100,果断dp写了一个过了,可是还是不理解为什么排列组合不过。接着问学长要了错的那组数据,才发现那组输入数据是错了,由于dp是标程,所以刚好过了。已经无语凝噎了

dp的写法应该不要多讲

#include<iostream>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll dp[110][110];
int main(){
    ll x,y,n,m;
    cin>>n>>m>>y>>x;
    n--,m--;
    x=n-x+1,y--;
   for(int i=0;i<=n;i++)
   for(int j=0;j<=m;j++){
       if(i==x&&j==y){
           dp[i][j]=0;
       }
       else if(i==0||j==0)
       dp[i][j]=1;
       else
       dp[i][j]=(dp[i-1][j]+dp[i][j-1])%mod;
   }
   cout<<dp[n][m]<<'\n';
   //while(1);
    return 0;
}

稍微讲一下排列组合怎么写,其实也很简单,就是高中简单排列组合知识。中间放了个不能过的地方,那么我们拿不考虑这个特殊点的路径条数减去必然经过这个点的路径条数就是我们要的答案了。不拿原题那个抽象的坐标系了,我们就假设要从(0,0)出发到(n,m),(x,y)是不能经过的,那么答案就是C(n+m,m)-C(x+y,x)*C(n-x+m-y,n-x),因为涉及取模操作,且会溢出,所以在算组合数的时候,除法操作用逆元处理

#include<iostream>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
ll exgcd(ll a,ll b,ll&x,ll&y){
    if(b==0){
        x=1,y=0;
        return a;
    }
    ll d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
ll inv(ll a,ll m){
    ll x,y;
    exgcd(a,m,x,y);
    return (x%m+m)%m;
}
ll C(ll n,ll m){
    ll ans=1;
    for(int i=1;i<=m;i++)
    ans=ans*(n-m+i)%mod,ans=ans*inv(i,mod)%mod;
   return ans;
}
int main(){
    ll x,y,n,m;
    cin>>n>>m>>y>>x;
    n--,m--,y--,x--;
    if(x==0&&y==m)
    cout<<0<<'\n';
    else
    cout<<((C(n+m,m)-C(n-x+y,y)*C(x+m-y,x)%mod)%mod+mod)%mod<<'\n';
    //while(1);
     
    return 0;
}

K

思维题:首先假设一次可以涂连续的num个相同颜色的格子:ans+=(num/k向上取整),自己在草稿纸上画一下就差不多了
考虑下无解情况,对于每一个连续需要涂色的区域(也就是这个区间中不会出现-1),若最大的相同颜色长度小于k,则一定无解

分类考虑下头尾相连以及相连的情况

代码看起来很长,其实很多就是代码复用罢了

#include <iostream>
#include<vector>
#include<map>
#include<string>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 2e4 + 10;
const ll mod = 1e9 + 7;
int a[N];
void solve()
{
    int n, m, k;
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int ans = 0;
    if (a[1] != a[n] || a[1] == -1) {
        int num = 0, ma = 0;
        for (int i = 1; i <= n; i++) {
            if (a[i] == -1) {
                ans += (num + k - 1) / k, num = 0;
                if (ma < k) {
                    cout << -1 << '\n';
                    return;
                }
                ma = 0;
            }
            else if (a[i] == a[i - 1])
                num++, ma = max(num, ma);
            else
                ans += (num + k - 1) / k, num = 1, ma = max(ma, num);
        }
        if (ma > 0 && ma < k)
        {
            cout << -1 << '\n';
            return;
        }
        ans += (num + k - 1) / k;
    }
    else {
        int num = 0, ma = 0;
        int p = 1;
        a[0] = a[n];
        for (int i = 2; i <= n; i++) {
            if (a[i] == a[i - 1])
                p = i;
            else
                break;
        }
        for (int j = 1; j <= n; j++) {
            int i = (j + p) % n == 0 ? n : (j + p) % n;
            if (a[i] == -1) {
                ans += (num + k - 1) / k, num = 0;
                if (ma < k) {
                    cout << -1 << '\n';
                    return;
                }
                ma = 0;
            }
            else if (a[i] == a[i - 1])
                num++, ma = max(num, ma);
            else
                ans += (num + k - 1) / k, num = 1, ma = max(ma, num);
        }
        if (ma > 0 && ma < k) {
            cout << -1 << '\n';
            return;
        }
        ans += (num + k - 1) / k;
    }
    cout << ans << '\n';
}
int  main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

L

挺裸的一个字典树了,但是没咋写过字典,写delete的时候忘记要把所有的父亲节点更新了,只更新了末端,导致补题时wa了。但是考场上连续无数发的运行错误真的很离谱,后面不断试发现好像内置son数组和外面的节点都不能大,不然就会爆运行错误而不是内存超限。

不会的话去学一下字典树应该就会写了

#include<iostream>
#include<string>
using namespace std;
typedef long long ll;
const int N = 1000010;
struct node {
    bool repeat;
    int son[30] = { 0 };
    int num = 0;
}t[N];
int cnt = 1;
void insert(char* s) {
    int now = 0;
    for (int i = 0; s[i] != '\0'; i++) {
        int ch = s[i] - 'a';
        if (t[now].son[ch] == 0)
            t[now].son[ch] = cnt++;
        now = t[now].son[ch];
        t[now].num++;
    }
}
int find(char* s) {
    int now = 0;
    for (int i = 0; s[i] != '\0'; i++) {
        int ch = s[i] - 'a';
        if (t[now].son[ch] == 0)return 0;
        now = t[now].son[ch];
    }
    return t[now].num;
}
void del(char* s, int x) {
    if (x == 0)
        return;
    int now = 0, len = 0;
    for (int i = 0; s[i+1] != '\0'; i++) {
        int ch = s[i] - 'a';
        if (t[now].son[ch] == 0)return;
        now = t[now].son[ch];
        t[now].num -= x;
        len++;
    }
    t[now].son[s[len] - 'a'] = 0;
}
int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        string op;
        cin >> op;
        char s[50] = { 0 };
        cin >> s;
        if (op[0] == 'i')
            insert(s);
        else if (op[0] == 's')
        {
            int ans = find(s);
            if (ans == 0)
                cout << "No Data" << '\n';
            else
                cout << ans << '\n';
        }
        else
            del(s, find(s));
    }
    //while(1);
    return 0;
}

总结

感觉不太好,首先是脑袋有点抽风,括号匹配和计算日期一起wa了几发,接着就是字典树,写完用了应该不到10分钟但是找错用了30+,路径数量那个题数据有问题是一方面吧,自己眼神不好也是一方面,开题策略也有点问题,狂干了1小时的h最后还是没写完,k题看没人做就不去做了,应该是可以做出来的,思维量也就cf大概1300-1400左右来着。

但是还是想吐槽下学校电脑,我u盘拷了个dev到电脑上然后打不开,折腾到最后拿vc2010写,然后发现老版本上补全还不如学校oj在线编辑器,甚至括号补全和高亮都没有,还有cpp的版本,最后在学校oj在线写了4个小时题,人都麻了,还有几次错了放到vc2010想测试一些样例,加了个while(1)固屏,改好了忘记删,TL了好几发。

总而言之就是该做的没做或者没做对,且罚时爆炸。写题罚时爆炸算是老毛病了,以后还是要练练这个方面。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您讲解一下 HNUCM 最小生成树(Prim)的算法过程。 首先,Prim 算法是一种贪心算法,用于求解带权无向连通图的最小生成树。下面是具体的算法过程: 1. 选取任意一个顶点作为起点,将其加入到生成树中。 2. 找到与已经加入到生成树中的顶点相邻的所有顶点,并计算它们与生成树的距离。 3. 从这些顶点中选取距离最小的顶点,将其加入到生成树中。 4. 重复步骤2和3,直到生成树中包含了所有的顶点。 在具体实现时,可以使用一个数组来记录每个顶点是否已经加入到生成树中,以及另一个数组来记录每个顶点与生成树的距离。每次选择距离最小的顶点时,可以使用一个最小堆来优化。 至于 HNUCM 最小生成树 Prim 算法的具体实现,可以参考以下代码: ```python import heapq def prim(graph): n = len(graph) visited = [False] * n distance = [float('inf')] * n distance[0] = 0 heap = [(0, 0)] result = 0 while heap: (d, u) = heapq.heappop(heap) if visited[u]: continue visited[u] = True result += d for v, weight in graph[u]: if not visited[v] and weight < distance[v]: distance[v] = weight heapq.heappush(heap, (weight, v)) return result ``` 这段代码实现了 HNUCM 最小生成树 Prim 算法的过程,其中 graph 是一个邻接表表示的带权无向连通图,每个元素是一个二元组 (v, w),表示从节点 u 到节点 v 有一条边权为 w 的边。算法的返回值是最小生成树的总权值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值