A. 卡片(5分)
分析:
简单题,直接从1开始处理每一个数,直到出现某一种卡片不够为止。
#include
using namespace std;
int main(){
int cnt[10],ans=1,flag=1;
for(int i=0;i<=9;i++) cnt[i]=2021;
while(flag){
int x=ans;
while(x){
int y=x%10; cnt[y]--;
if(cnt[y]<0) flag=0;
x/=10;
}
if(!flag) break;
ans++;
}
cout<<ans-1<<endl;
return 0;
}
B. 直线(5分)
分析:
通过两点式我们知道(x-x1)/(x2-x1)=(y-y1)/(y2-y1)。将x1= =x2和y1==y2的情况分离出来单独讨论,显然这种情况的结果是20+21,其它的通过化简可知最后的直线方程为
(y2-y1)* x+(x1-x2)* y+(x2* y1-x1* y2)。只要我们求出了三个参数A=y2-y1,B=x1-x2以及C=x2* y1-x1* y2,我们便可以化简后放在一个容器中vector中,然后再将容器vector放在集合set中,最后的结果就是集合的大小+20+21。
#include
using namespace std;
inline int gcd(int a,int b){
if(b==0) return a;
return a%b?gcd(b,a%b):b;
}
struct Point{
int x,y;
};
set<vector<int> >s;
vector<Point>e;
int main(){
int ans,m=20,n=21;
for(int i=0;i<m;i++)
for(int j=0;j<n;j++) e.push_back({i,j});
for(int i=0;i<e.size();i++){
for(int j=i+1;j<e.size();j++){
Point p1=e[i],p2=e[j];
int x1=e[i].x,y1=e[i].y,x2=e[j].x,y2=e[j].y;
if(x1==x2||y1==y2) continue;
int a=y2-y1,b=x1-x2,c=x2*y1-x1*y2;
int temp=gcd(a,gcd(b,c));
vector<int>f; f.push_back(a/temp);
f.push_back(b/temp); f.push_back(c/temp);
s.insert(f);
}
}
ans=s.size()+m+n;
cout<<ans<<endl;
return 0;
}
C.货物摆放(10分)
分析:
一个10的16次方的数,它的因子数最多不超过两百,所以我们可以用O(sqrt(n))的时间复杂度求出它的所有的因子(个数假设为x),然后直接三重循环O(x^3) 遍历每一种情况,要是符合则cnt++。
#include
using namespace std;
typedef long long ll;
int main(){
ll n=2021041820210418,cnt=0;
vector<ll>g;
for(ll i=1;i*i<=n;i++){
if(n%i==0) {
g.push_back(i); g.push_back(n/i);
}
}
for(int i=0;i<g.size();i++){
for(int j=0;j<g.size();j++){
for(int k=0;k<g.size();k++)
if(g[i]*g[j]*g[k]==n) cnt++;
}
}
cout<<cnt<<endl;
return 0;
}
D.路径(10分)
分析:
很显然是一道Dijkstra算法的模板题。由于节点个数n=2021,所以最普通的时间复杂度为O(n^2)的Dijkstra算法就可以得出结果;当然我习惯使用优先队列优化后的时间复杂度为O(mlog n)的Dijkstra算法。以下题解为优化后的算法。而对于两个数a和b的最小公倍数来说,我们可以通过求出最大公因数gcd(a,b),然后最小公倍数lcm(a,b)便是a*b/gcd(a,b)。
#include
using namespace std;
#define INF 0x7fffffff
const int maxn=1e4+10;
typedef long long ll;
struct Edge{
int v,w;
bool operator < (const Edge&b)const{
return w>b.w;
}
};
ll d[2050],vis[2050];
vector<Edge>g[maxn];
inline gcd(int a,int b){
return a%b?gcd(b,a%b):b;
}
inline lcm(int a,int b){
return a*b/gcd(a,b);
}
void Dijkstar(){
priority_queue<Edge>q;
q.push({1,0}); d[1]=0;
while(!q.empty()){
Edge x=q.top();q.pop();
int u=x.v;
if(vis[u]) continue;
vis[u]=1;
for(int i=0;i<g[u].size();i++){
int v=g[u][i].v,w=g[u][i].w;
if(d[u]+w<d[v]) {
d[v]=d[u]+w;
q.push({v,d[v]});
}
}
}
cout<<d[2021]<<endl;
}
int main(){
int n=2021;
for(int i=1;i<=2021;i++) d[i]=INF,vis[i]=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(j-i<=21) {g[i].push_back({j,lcm(j,i)});g[j].push_back({i,lcm(j,i)});}
}
}
Dijkstar();
return 0;
}
E. 回路计数(15分)
分析:
F. 砝码称重(15分)
分析:
第一道编程题,肯定不会太难,直接暴力。
由于砝码的总重量w为1~100000,我们可以开一个标记数组visit[100010],记录是否某一个重量 i 可以达到。由于砝码的个数n为1~100,我们可以遍历每一个砝码,并且开通辅助动态数组vector< int > g,g用来存储现在可以称出的重量 。对每一个砝码进行以下三种处理:
1. 只称当前砝码重量,若未标记则加入g中。
2. 将当前砝码重量与g中每一个可能出现的砝码放在不同的两边,即重量相减,并取绝对值,若未标记则加入g中。
3. 将当前砝码重量与g中每一个可能出现的砝码放在同一边,即重量相加,若未标记则加入g中。
时间复杂度为:O(n*w)
#include
#include
#include
#include
using namespace std;
#define INF 0x7fffffff
const int maxn = 1e5 + 10;
int a[105];
bool visit[maxn];
vector<int>g;
int main(){
int n; cin >> n;
for (int i = 1; i <= maxn-10; i++) visit[i] = false;
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + 1 + n);
g.push_back(a[1]); visit[a[1]] = true;
for (int i = 2; i <= n; i++) {
int len=g.size();
for (int j = 0; j < len; j++) {
int temp1 = a[i], temp2 = abs(a[i] - g[j]), temp3 = a[i] + g[j];
if (temp1 > 0 && !visit[temp1]) visit[temp1] = true, g.push_back(temp1);
if (temp2 > 0 && !visit[temp2]) visit[temp2] = true, g.push_back(temp2);
if (temp3 > 0 && !visit[temp3]) visit[temp3] = true, g.push_back(temp3);
}
}
cout << g.size();
return 0;
}
分析:
一道位运算的题。首先,假设Alice的最后数字为A,Bob是B,如果是平局的话,那么A= =B,即A^B==0 。所以当所有的输入进行异或结果是0的话,则平局,否则不是平局。 我们用一个数字cnt[maxn]来记录二进制从高位到低位的每一个数字出现次数;不难发现,若最高位只出现一次,结果一定是Alice赢,因为Alice先手。 其他情况我们分开讨论:
1. 若最高位出现奇数次,且n为奇数:则一定是先手赢
2. 若最高位出现奇数次,且n为偶数,则一定是后手赢
3. 若最高位出现偶数次,则分析次高位,以此类推。
#include
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
int cnt[maxn];
int main()
{
int t; cin>>t;
while(t--){
memset(cnt,0,sizeof(cnt));
int n,ans=0; cin>>n;
for(int i=1;i<=n;i++){
int x; cin>>x;
for(int i=0;i<20;i++){
if(x&(1<<i)) cnt[i]++;
}
ans=ans^x;
}
bool flag=false;
if(ans==0) cout<<0<<endl;
else{
for(int i=19;i>=0;i--){
if(cnt[i]==1) {
ans=1;
break;
}
else if(cnt[i]&1){
if(n&1) ans=1;
else ans=-1;
break;
}
else continue;
}
cout<<ans<<endl;
}
}
return 0;
}
分析:
一道简单深搜(dfs)题。对于每一棵子树的父节点u而言,他的最大长度就是子树v的个数加上它所有子树中所能达到的最大长度;所以我们只需要对每一个子节点dfs一次,记录它的最大长度,最后结果相加即可。
时间复杂度:O(n)
#include
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
vector<int>g[maxn];
int ans=0;
int dfs(int u){
int res=0;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
res=max(res,dfs(v));
}
res+=g[u].size();
return res;
}
int main()
{
int n;cin>>n;
for(int i=2;i<=n;i++){
int x; cin>>x;
g[x].push_back(i);
}
cout<<dfs(1)<<endl;
return 0;
}