(狙击赛第一场记录)
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;
}
(狙击赛第二场记录)
- -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;
}
- -Three Integers
题意:
给你三个非负整数a, b,以及c。找出三个正整数x, y,以及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;
}