(狙击赛第一,二场记录)

狙击赛第一场记录

G - Shinyruo and KFC Gym

题意:

给你n种不同的食物,每种食物有a i多个,食物个数不超过1e5,现在一共有m个人,1≤k≤m ,每种食物最多拿一个,每个人可以拿多种食物,输出食物分配方案。

题解:

关键语句,食物个数不超过1e5,说明给你的数据会有很多重复的样例,对于每个重复的样例我们可以采用快速幂这样累加答案,而且去重过后的数组长度不会太大,所以完全可以写暴力。枚举人数来写。

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 100, mod = 998244353;

int arr[N], sum[N], fac[N], infac[N];

int ksm(int a, int b)//快速幂

{

int ans = 1;

while (b)

{

if (b & 1) ans = (ans * a) % mod;

a = (a * a) % mod;

b >>= 1;

}

return ans;

}

int C(int n, int m)//组合数公式

{

if (m < 0 || n < m) return 0;

return fac[n] * infac[m] % mod * infac[n - m] % mod;

}

void init()//预处理阶乘,逆元

{

fac[0] = 1;

for (int i = 1; i <= N - 1; i++) fac[i] = (fac[i - 1] * i) % mod;

for (int i = 0; i <= N - 1; i++) infac[i] = ksm(fac[i], mod - 2);

}

int  main()

{

init();

int n, m;

cin >> n >> m;

for (int i = 1; i <= n; i++) cin >> arr[i], sum[arr[i]]++;

sort(arr + 1, arr + 1 + n);//排序不影响结果,每个数都会算到的

int r = unique(arr + 1, arr + 1 + n) - arr - 1;//去重,也可以选用map

int l = 1;

while (arr[l] == 0) l++;//找到第一个不为0的数,题目说了

for (int i = 1; i <= m; i++)

{

int ans = 1;

for (int j = l; j <= r; j++) ans = (ans * ksm( C(i, arr[j]), sum[arr[j]]) ) % mod;

cout << ans % mod << endl;

}

return 0;

}

狙击赛第二场记录

  1. -Sum of Log

题意:

就是满足那个公式,当然是求和,但是会卡时间复杂度。

题解:

没有思路,别人用数位dp做,超出理解范围。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

            

const int N = 1e5 + 5, P = 1e9 + 7;

int x, y, ans, f[35][2][2][2];

int dfs(int len, int lm1, int lm2, int ff)

{

  if (len == -1)

  {

    return 1;

  }

  if (~f[len][lm1][lm2][ff]) return f[len][lm1][lm2][ff];//ff=1说明这是最高位

  int up1 = lm1 ? x >> len & 1 : 1;

  int up2 = lm2 ? y >> len & 1 : 1;

  int res = 0;

  for (int i = 0; i <= up1; i++)

  {

    for (int j = 0; j <= up2; j++)

{

      if ((i & j)) continue;

      if (ff)

  {//如果是最高:

        if (i | j)

{//保证最高位为1.

          res = (res + dfs(len - 1, lm1 && i == up1, lm2 && j == up2, 0)) % P;

        }

      } else

  {

        res = (res + dfs(len - 1, lm1 && i == up1, lm2 && j == up2, 0)) % P;

      }

    }

  }

  f[len][lm1][lm2][ff] = res;

  return res;

}

int main() {

  int T;

  scanf("%d", &T);

  while (T--) {

    scanf("%d %d", &x, &y);

    int nx = x, n = 0, ny = y, m = 0;

    while (nx) ++n, nx >>= 1;

    while (ny) ++m, ny >>= 1;

    ans = 0;

    memset(f, -1, sizeof f);

    

    for (int i = max(n, m); i >= 0; i--)

{

        //枚举二进制位的长度。

      ans = (ans + 1LL * dfs(i, i >= n - 1 , i >= m - 1, 1) * (i + 1) % P) % P;

    }

    printf("%d\n", ans);

  }

  return 0;

}

  1. -Three Integers

题意:

给你三个非负整数ab,以及c。找出三个正整数xy,以及z那满足了x mod y=a, y mod z = b ,z mod x=c.

题解:

直接暴力

代码:

#include<bits/stdc++.h>

using namespace std;

#define INF 0x3f3f3f3f

int main()

{

    int t;

cin >> t;

while (t--)

{

int a, b, c;

cin >> a >> b >> c;

int ans = 1e9;

int A = -1, B = -1, C = -1;

for (int c1 = 1; c1 <= 2 * a; ++c1)

{

for (int c2 = c1; c2 <= 2 * b; c2 += c1)

{

for (int i = 0; i < 2; ++i)

{

int c3 = c2 * (c / c2) + i * c1;

int res = abs(c1 - a) + abs(c2 - b) + abs(c3 - c);

if (ans > res)

{

ans = res;

A = c1;

B = c2;

C = c3;

}

}

}

}

cout << ans << endl << A << " " << B << " " << C << endl;

}

    return 0;

}

G -- Traveling in the Grid World

题意:

平面上有(0,0)(n,m)总共(n+1)∗(m+1)个格点。任取两点(x,y),(x′,y′)连成线段,若线段上没有其他格点,则称这条线段为一条合法边。求(0,0)(n,m)的最短路径,要求相邻两条边的斜率不能相等。

题解:

必须满足有两条边,并且两条边,刚好构成一个三角形。

首先考虑线段上有其他格点的情况,由于两边之和大于第三边,线段数为k的路径一定比线段数为k−1的路径长。然后,对于边数为2的路径ABC,若AB上存在格点D,则路径ADC一定小于路径ABC。所以最终线段ABC上一定无格点。所以对于n,m不互质的情况,最短路径边数一定为2。

补充重点:

__gcd()函数

__gcd(x,y);
int、long long类型都可以,需要注意的是两个类型必须要相同,还有不能用浮点型,当然手写gcd函数也是可以的,它头文件是algorithm。
例题,当分字分母最大公约数是 1,这个分数称为既约分,的时候就会进行 ans++;

代码示例:

#include <iostream>

#include <algorithm>

using namespace std;

int main()

{

int ans=0;

for(int i=1;i<=2020;i++)

{

for(int j=1;j<=2020;j++)

{

if(__gcd(i,j)==1)

ans++;

}

}

cout<<ans<<endl;

return 0;

}

代码:

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const int inf=1e9;

const int maxn=1e5+5;

int T,n,m;

double cal(int x,int y)//用欧几里得计算出两点之间的长度

{

return sqrt(1.0*x*x+1.0*y*y);

}

double jud(int x,int y)

{

if(y<0||y>m) //超出单元格

return inf;

if(__gcd(x,y)!=1||__gcd(n-x,m-y)!=1) //表示两条直线的最大公因数都不是的话

return inf;

if(x*m==y*n) //两个斜率相等的话

return inf;

return cal(x,y)+cal(n-x,m-y);

}

int main()

{

scanf("%d",&T);

while(T--)

{

scanf("%d%d",&n,&m);

double ans=inf;

if(__gcd(n,m)==1)

{

ans=sqrt(1.0*n*n+1.0*m*m);

}

else

{

for(int i=0;i<=n;i++)

{

int j=i*m/n;

ans=min(ans,jud(i,j));

ans=min(ans,jud(i,j-1));

ans=min(ans,jud(i,j+1));

}

}

printf("%.15f\n",ans);//注意小数点后面的个数

}

return 0;

}

H --  Occupy the Cities

题意:

一个字符串,JB想要把所有0,全部变成1,每次只能把其中相邻的一个位置,改变一下,问你,最快几次搞定。

题解:

贪心不好做,因为需要每次考虑左右两边,所以需要分四种情况,然后每次都需要判断,一步贪心,需要写一套。

可以使用二分算法,每次找出来,如果说超过了的部分,就返回false。

补充重点:

最小值的问题。可以考虑dp或者二分,当然一定要有某种特定的规律,去写出判断条件或者状态转移方程,否则都白搭。

代码:

#include<bits/stdc++.h>

using namespace std;

const int  N = 1e6+5;

const int inf=1e9;

int T,n,m;

int va[N];

char s[N];

int suml[N],sumr[N];

int vis[N];

bool check(int mid)

{

for(int i=1;i<=n;i++) vis[i] = 0;//初始化

for(int i=1;i<=n;i++)

{

if(s[i]=='0')

{

int minn = min(i-suml[i],sumr[i]-i)+1;

if(minn<=mid) continue;

if(minn==mid+1) //最多能处理这些

{

if(suml[i]>=1&&!vis[suml[i]]) //尽量用左边的1,右边的留给别人,这样最贪心

{

vis[suml[i]] = 1;

continue;

}

if(sumr[i]<=n&&!vis[sumr[i]])

{

vis[sumr[i]] = 1;

continue;

}

}

return false;

}

}

return true;

}

int main()

{

cin>>T;

while(T--)

{

cin>>n;

cin>>s+1;

int maxnl = -inf,maxnr = inf;

for(int i=1;i<=n;i++)//从左向右标记

{

if(s[i]=='0') suml[i] = maxnl;

else maxnl = i;

}

for(int i=n;i>=1;i--)//从右向左标记

{

if(s[i]=='0') sumr[i] = maxnr;

else maxnr = i;

}

int l = 0,r = n;

while(l<r)//进行二分

{

int mid = (l+r)>>1;

if(check(mid)) r = mid;

else l = mid+1;

}

cout<<l<<"\n";

}

return 0;

}

I -- Buy and Delete

题意:

有一个有向边集E,每个边有一个花费p,一开始有一个空图G,Alice有c元钱,现在她需要从边集E中选出来一些边添加到G中,且这些边的总花费不能超过c。Bob需要对图G进行删边操作,每次Bob可以从图G中选出一些边满足这些边是无法组成环的,然后从图中删除它们,直到G中没有边。现在Alice需要选出一些边使得Bob的操作次数最多,Bob删除边时会进行最优操作。输出Bob可能的最多操作次数

题解:

图论题:没思路---。

其他人题解:

分情况来看

图G没有边(即Alice买不起任何一条边),Bob需要操作0次。

该图无环,Bob只需要操作一次。

该图有环,其实不管该图有几个环,只要有环,Bob进行2次操作就可以删完所有的边。

把图中所有的边(u→v)分为2类,一类是u>v的边,一类是u<v的边。

显然,每一类边内部是不会有环存在的,并且这两类是覆盖了图中所有边的,因此删除2次就可以删完图中所有的边。

所以Alice只需要尽可能凑出一个环来就行,因此只需要找到图中的最小环,设该最小环的总权值为w,如果c<w,就1,否则就是2。(当然还有一个一条边都买不起的情况0)

补充重点:

Dijkstra()算法,算法课里有,但是本题需要找到最小环。

代码:

#include<bits/stdc++.h>

using namespace std;

#define INF 0x3f3f3f3f

typedef long long LL;

typedef pair<int, int> pii;

const int N = 2010, M = 5010;

int n, m, tot;

int idx, head[N], e[M], w[M], ne[M];

int dist[N][N];

bool vis[N];

struct Node { int u, v, w; } edges[M];//存下来所有的边

void add(int u, int v, int _w)

{

    e[++ idx] = v, w[idx] = _w, ne[idx] = head[u], head[u] = idx;

}

void dijkstra(int s, int dist[])

{

    memset(dist, 0x3f, N * 4);

    memset(vis, 0, sizeof vis);

    priority_queue<pii> Q;

    Q.push({0, s});

    dist[s] = 0;

    while(Q.size())

    {

        int u = Q.top().second;

        Q.pop();

        if(vis[u])  continue;

        vis[u] = true;

        for(int i = head[u]; i; i = ne[i])

        {

            int v = e[i];

            //if(u == a && v == b) continue;

            if(!vis[v] && dist[v] > dist[u] + w[i])

            {

                dist[v] = dist[u] + w[i];

                Q.push({-dist[v], v});

            }

        }

    }

}

int main()

{

    scanf("%d%d%d", &n, &m, &tot);

    bool flag = false;

    for(int i = 1; i <= m; i ++)

    {

        int u, v, w;

        scanf("%d%d%d", &u, &v, &w);

        add(u, v, w);

        edges[i] = {u, v, w};

        if(tot >= w)    flag = true;

    }

    if(!flag) //一条边也买不起

    {

        puts("0");

        return 0;

    }

    //n次最短路,求任意两点之间的最短距离

    for(int  i = 1; i <= n; i ++)   dijkstra(i, dist[i]);

    int res = INF; //求最小环

    for(int i = 1; i <= m; i ++)

    {

        int a = edges[i].u, b = edges[i].v, c = edges[i].w;

        res = min(res, c + dist[b][a]);

    }

    puts(tot < res ? "1" : "2");

    return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值