2023牛客寒假算法基础集训营1个人补题 A C D H K L M E F G B
A
题意
给定一个长度为10的得分结果,问至少在第几轮可以确定比赛结果。
思路
签到,就是贪心的思考在每一次结果出来后在劣势方后续最优,优势方最劣的情况下是否有可能翻盘即可。要注意的点就是每次一定要贪心考虑双方的后续,没注意wa了一发(
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
typedef pair<int, int> PII;
int n;
int a[N];
void solve() {
string s;
cin>>s;
s=" "+s;
int p=0,q=0;
for(int i=1;i<=10;i++) {
if(i%2) {
if(s[i]=='1') {
p++;
}
if((10-i)/2+1+q<p||(10-i)/2+p<q) {
cout<<i<<"\n";
return;
}
}
else {
if(s[i]=='1') {
q++;
}
if((10-i)/2+p<q||(10-i)/2+q<p) {
cout<<i<<"\n";
return;
}
}
}
if(p!=q) cout<<10<<"\n";
else cout<<"-1\n";
}
signed main() {
IOS;
int t = 1;
cin >> t;
for (int i = 1; i <= t; i++) {
solve();
}
}
C
题意
给一个H系数概念(有至少H篇论文的引用量大于等于H),并给定n篇论文的引用量,问分配最大的时候所有h系数之和是多少。
思路
阅读理解,理解完题目后第一反应是排序的贪心,因为引用为1的论文最好就只给一个人,这样保证不会浪费,然后写着写着突然发现好像就是非0的论文篇数,加上感觉贪心有点小问题(赛时没有办法找到详细证明证明这样分n个人一定够用),猜了一发就直接过了。
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
typedef pair<int, int> PII;
int n;
int a[N];
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + n + 1);
int num = upper_bound(a + 1, a + n + 1, 0) - a ;
cout<<n-num+1<<"\n";
}
signed main() {
IOS;
int t = 1;
cin >> t;
for (int i = 1; i <= t; i++) {
solve();
}
}
D
题意
给一个矩形,再给定一个点,要求找到另外一个点使得和之前给定点的交集并集比值最大值。
思路
分类讨论,画图可以发现,当不管在矩形内部还是外部的时候,另外的一个点一定是在矩形的四个点上,当然,也可以再细化讨论,甚至可以直接O(1)做出来。
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
typedef pair<int, int> PII;
int n,m;
int a[N];
void solve() {
int x,y;
int xp,yp;
cin>>x>>y>>xp>>yp;
int dx[]={0,x,0,x};
int dy[]={y,0,0,y};
double res=0;
if(xp<=x&&yp<=y) {
for(int i=0;i<4;i++) {
res=max(res,(1.0*abs(dx[i]-xp)*abs(dy[i]-yp))/(x*y));
}
}
else {
if(xp<=x) {
res=max((1.0*xp*y)/(xp*(yp-y)+x*y),(1.0*(x-xp)*y)/((x-xp)*(yp-y)+x*y));
}
else if(yp<=y) {
res=max((1.0*yp*x)/((xp-x)*yp+x*y),(1.0*(y-yp)*x)/((y-yp)*(xp-x)+x*y));
}
else {
res=(1.0*x*y)/(xp*yp);
}
}
cout<<fixed<<setprecision(9)<<res<<"\n";
}
signed main() {
IOS;
int t = 1;
cin >> t;
for (int i = 1; i <= t; i++) {
solve();
}
}
H
题意
给n * n个拼图,现在拿走一块,已知每块拼图基础成本为10,一个凹槽-1,一个凸包+1,求拿走的一块成本是多少。
思路
可以发现很明显就是个思维题,因为不管怎么拼凹槽和凸包数量一定相等,那么只要记录剩下拼图需要凹槽和凸包的个数就够了。(注意读入,第一发写成n,第二发写成n * n,白吃两发罚时)
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
typedef pair<int, int> PII;
int n;
int a[N];
void solve() {
cin>>n;
int res=0;
for(int i=1;i<=n*n-1;i++) {
string s;
cin>>s;
for(int j=0;j<4;j++) {
if(s[j]=='1') res--;
if(s[j]=='2') res++;
}
}
cout<<10-res<<"\n";
}
signed main() {
IOS;
int t = 1;
cin >> t;
for (int i = 1; i <= t; i++) {
solve();
}
}
K
题意
给定01字符串长度和1的个数,求区间长度为三且1个数少于0个数的01字符串有多少个区间长度为三的个数。
思路
打表,感觉贪心的想就是将最坏的往后堆,前面由100组成循环节,最后把剩下的堆在后面,结果打了几组小数据发现是对的,就直接过了。
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
typedef pair<int, int> PII;
int n, m;
int a[N];
void solve() {
cin >> n >> m;
string s;
s = " ";
int num1 = m, num0 = n - m;
for (int i = 1; i <= n; i++) {
if (i % 3 == 1) {
if (num1 > 0) {
num1--;
s = s + '1';
}
else break;
}
else {
if (num0 > 0) {
num0--;
s = s + '0';
}
else break;
}
}
while(num1!=0) {
num1--;
s=s+'1';
}
while(num0!=0) {
num0--;
s=s+'0';
}
int res=0;
for(int i=3;i<=n;i++) {
if(s[i]+s[i-1]+s[i-2]-3*'0'>1) res++;
}
cout<<res<<"\n";
}
signed main() {
IOS;
int t = 1;
//cin >> t;
for (int i = 1; i <= t; i++) {
solve();
}
}
L
题意
有五个团队,每个团队四个人,每次可以问目标是否在某个团队或者是不是某个人,求问话次数的期望。
思路
数学题,就是直接算概率。
代码
#include<bits/stdc++.h>
using namespace std;
signed main() {
cout<<32<<"\n";
}
M
题意
m个仙贝,n个人,给一个人x个仙贝增加好感x/剩余数量,每个人只能给一次,求最大好感之和。
思路
被标题误导,一直在试图倒着想找规律,由于最后一次一定增加好感为1,试图根据这个往前推,后来打表的时候尝试dp打表,发现二维dp完全可写,但是时间复杂度 O ( n 3 ) O(n^3) O(n3)卡的有点极限。
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
typedef pair<int, int> PII;
int n,m;
int a[N];
double f[1000][1000];
void solve() {
cin>>n>>m;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
for(int k=1;k<=j;k++) {
f[i][j]=max(f[i][j],f[i-1][j-k]+1.0*k/j);
}
}
}
double res=0;
for(int i=1;i<=m;i++) {
res=max(res,f[n][i]);
}
cout<<fixed<<setprecision(9)<<res<<"\n";
}
signed main() {
IOS;
int t = 1;
//cin >> t;
for (int i = 1; i <= t; i++) {
solve();
}
}
E
题意
给定两个三角形,问能否不通过翻转得到。
思路
计算几何,直接套板子就完了,写完wa了好几发,卡了最后将近两个小时,甚至一度怀疑板子错了,结果赛后发现题目中说的L形不一定是直角,而我没看到给定了先后的固定点都是在中间,导致一直wa,最后把判断直角去掉直接过了。。。另外后来又尝试了叉乘之外的一种判断方法,直接判断对应角度,感觉是因为加减比较的时候有可能要加减2pi最后丢失了精度。只能用叉乘判是否同号ac。
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
typedef pair<int, int> PII;
int n, m;
int a[N];
const double eps = 1e-7;
struct Point {
double x, y;
Point(double x = 0, double y = 0): x(x), y(y) { } //构造函数
};
typedef Point Vector;
double Cross(Vector A, Vector B) {
return A.x * B.y - B.x * A.y;
}//叉乘
//!点 - 点 = 向量(向量BC = C - B)
Vector operator - (Point A, Point B) {
return Vector(A.x - B.x, A.y - B.y);
}
double Dot(Vector A, Vector B) {
return A.x * B.x + A.y * B.y;
}//点积(满足交换律)
double Length(Vector A) {
return sqrt(Dot(A, A));
}//长度
double Polar_angle(Vector A) {
return atan2(A.y, A.x);
}//与x轴夹角
void solve() {
Point p[5],q[5];
for(int i=1;i<=3;i++) {
cin>>p[i].x>>p[i].y;
}
for(int i=1;i<=3;i++) {
cin>>q[i].x>>q[i].y;
}
if(Length(p[3]-p[2])<Length(p[2]-p[1])) {
swap(p[1],p[3]);
}
if(Length(q[2]-q[3])<Length(q[2]-q[1])) {
swap(q[1],q[3]);
}
if(fabs(Length(q[1]-q[2])-Length(q[2]-q[3]))<eps) {
cout<<"NO\n";
}
else {
if((Cross(p[1]-p[3],p[3]-p[2])>0&&Cross(q[1]-q[3],q[3]-q[2])>0)||
(Cross(p[1]-p[3],p[3]-p[2])<0&&Cross(q[1]-q[3],q[3]-q[2])<0)||
(fabs(Cross(p[1]-p[2],p[3]-p[2]))<eps&&fabs(Cross(q[1]-q[2],q[3]-q[2]))<eps)) {
cout<<"NO\n";
}
else cout<<"YES\n";
}
}
signed main() {
IOS;
int t = 1;
cin >> t;
for (int i = 1; i <= t; i++) {
solve();
}
}
F
题意
给一个无向图,可以从任意点出发到任意点,同时可以在途径点下蛋,但是下蛋之后就不能再走到那个点,问有多少种可能的起点终点方案(S,T)
思路
仔细想一下就会发现其实和下蛋多少基本上没关系,只要保证下的蛋在一个连通块内就一定有解,否则无解,所以可以用dfs或者并查集先维护所有连通块,最后直接求解。
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
typedef pair<int, int> PII;
int n, m;
int a[N];
int p[N];
int vis[N];
vector<int>g[N];
void dfs(int u, int pre) {
if (vis[u]) return;
vis[u] = pre;
for (auto v : g[u]) {
dfs(v, pre);
}
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
g[v].push_back(u);
g[u].push_back(v);
}
for (int i = 1; i <= n; i++) cin >> a[i], p[i] = i;
for (int i = 1; i <= n; i++) {
dfs(i, i);
}
map<int, int>mp;
for (int i = 1; i <= n; i++) {
mp[vis[i]]++;
}
bool f = 0;
for (int i = 1; i <= n; i++) {
if (a[i]) f = 1;
}
int res = 0;
if (!f) {
for (auto [i, j] : mp) res += j * j;
} else {
int cnt = 0;
int id;
id = -1;
for (int i = 1; i <= n; i++) {
if (a[i]) {
cnt++;
if (id == -1 || id == vis[i]) id = vis[i];
else f = 0;
}
}
if (f) res = mp[vis[id]] * mp[vis[id]];
}
cout << res << "\n";
}
signed main() {
IOS;
int t = 1;
//cin >> t;
for (int i = 1; i <= t; i++) {
solve();
}
}
G
题意
你有一个长为n的数组a,你需要支持以下两种操作:
1、输入l,r,k,对区间[l,r]中所有数字执行 a i = f ( a i ) a_i=f(a_i) ai=f(ai)操作k次, f ( x ) = r o u n d ( 10 s q r t ( x ) ) f(x)=round(10sqrt(x)) f(x)=round(10sqrt(x)),round为四舍五入函数。
2、输出当前数组所有数字的和。
思路
第一反应线段树,后来发现k的次数给的太多了有点不对,又细看了下打了个表发现了规律:当数小于等于99,在12次操作之内一定变成99且不发生变化;大于等于100时也是在12次内变成100不变。于是我就尝试直接进行了数据结构的模拟,并且当数等于99,100时就可以不再维护它了,那么其实真正计算的次数并不多,但是比较复杂,大量使用了数据结构。
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
typedef pair<int, int> PII;
int n,m;
int a[N];
int sum;
set<PII>num;
int f(int tmp) {
int cnt=0;
while(tmp!=99&&tmp!=100) {
tmp=10*sqrt(1.0*tmp)+0.5;
cnt++;
//cout<<tmp<<"\n";
}
return cnt;
}
void solve() {
cin>>n>>m;
for(int i=1;i<=n;i++) {
cin>>a[i];
sum+=a[i];
}
for(int i=1;i<=n;i++) {
if(a[i]!=99&&a[i]!=100&&a[i]!=0) {
int k=f(a[i]);
num.insert({i,k});
}
}
for(int i=1;i<=m;i++) {
int op;
cin>>op;
if(op==1) {
int l,r,k;
cin>>l>>r>>k;
vector<int>v;
for(auto it=num.lower_bound({l,0});it!=num.end();it=next(it)) {
if(it->first<=r) {
if(it->second<=k) {
v.push_back(it->first);
if(a[it->first]>=100) sum=sum-a[it->first]+100;
else sum=sum-a[it->first]+99;
int cnt=it->second;
int id=it->first;
num.erase(it);
num.insert({id,0});
}
else {
int cnt=it->second;
int id=it->first;
num.erase(it);
num.insert({id,cnt-k});
sum-=a[id];
int kk=k;
while(kk--) {
a[id]=10*sqrt(1.0*a[id])+0.5;
}
sum+=a[id];
}
}
else break;
}
for(auto i:v) {
num.erase({i,0});
}
}
else {
cout<<sum<<"\n";
//for(int i=1;i<=n;i++) cout<<a[i]<<" \n"[i==n];
}
}
}
signed main() {
IOS;
int t = 1;
//cin >> t;
for (int i = 1; i <= t; i++) {
solve();
}
}
B
思路
题意有点复杂,直接说赛后的补题思路,其实看到数据范围就可以想到四维dp了,但是难点在于如何优化时间复杂度,因为朴素版本的时间复杂度是 O ( n m x 2 y 2 ) O(nmx^2y^2) O(nmx2y2),后来看题解发现,我们可以通过使用前缀和的方式,在每次枚举完前n队m比分的时候直接前缀和预处理出来后续转移所需的式子,这样就可以将复杂度降低到 O ( n m x y ) O(nmxy) O(nmxy)
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 998244353 ;
int f[2][121][121][121] ;
int sum1[121][121][121], sum2[121][121][121], sum3[121][121][121] ; // sum1对角线 sum2上三角 sum3 下三角
void add(int &x, int y) {
x += y ;
if (x >= mod) x -= mod ;
}
void del(int &x, int y) {
x -= y ;
if (x < 0) x += mod ;
}
void solve() {
int n, m, x, y ;
cin >> n >> m >> x >> y ;
f[0][0][0][0] = 1 ;
int win = n * 3 ;
for (int i = 1, op = 1; i <= n; ++i, op ^= 1) {
memset(f[op], 0, sizeof(f[op])) ;
memset(sum1, 0, sizeof(sum1)) ;
memset(sum2, 0, sizeof(sum2)) ;
memset(sum3, 0, sizeof(sum3)) ;
for (int s = 0; s <= win; ++s)
for (int a = 0; a <= x; ++a) for (int b = 0; b <= y; ++b) {
sum1[s][a][b] = f[op ^ 1][s][a][b] ;
if (a > 0 && b > 0) add(sum1[s][a][b], sum1[s][a - 1][b - 1]) ;
if (b > 0) {
add(sum3[s][a][b], sum3[s][a][b - 1]) ;
add(sum3[s][a][b], sum1[s][a][b]) ;
} else sum3[s][a][b] = f[op ^ 1][s][a][b] ;
//cout<<i<<' '<<s<<' '<<a<<' '<<b<<' '<<sum1[s][a][b]<<' '<<sum3[s][a][b]<<'\n' ;
}
for (int s = 0; s <= win; ++s)
for (int b = 0; b <= y; ++b) for (int a = 0; a <= x; ++a) {
if (a > 0) {
add(sum2[s][a][b], sum2[s][a - 1][b]) ;
add(sum2[s][a][b], sum1[s][a][b]) ;
} else sum2[s][a][b] = f[op ^ 1][s][a][b] ;
//cout<<i<<' '<<s<<' '<<a<<' '<<b<<' '<<sum2[s][a][b]<<'\n' ;
}
for (int s = 0; s <= win; ++s)
for (int a = 0; a <= x; ++a) for (int b = 0; b <= y; ++b) {
if (s >= 1) add(f[op][s][a][b], sum1[s - 1][a][b]) ; //平
if (b > 0) add(f[op][s][a][b], sum3[s][a][b - 1]) ; //输
if (s >= 3 && a > 0) add(f[op][s][a][b], sum2[s - 3][a - 1][b]) ; //胜
}
}
int ans = 0 ;
for (int i = m; i <= win; ++i) add(ans, f[n & 1][i][x][y]) ;
cout << ans << '\n' ;
}
signed main() {
IOS;
int t = 1;
// cin >> t;
for (int i = 1; i <= t; i++) {
solve();
}
}