第二届“清华社杯”大学生算法大赛个人题解

A题 变化的矩形

【题意】有一个面积为 S (1 < S < 1e9) 的矩形。在我们对其进行以下操作后,您必须算出它的新面积大小:

1、长度增加 50%

2、宽度减少 50%

#include <bits/stdc++.h>
using namespace std;
int main(){
    int n; cin >>n;
    long double ans = 0.75*n;
    cout << fixed << setprecision(6) << ans;
    return 0;
}

B题 吃鸡梦之队

【题意】有来自 4 台服务器的 N (4 ≤ N ≤ 1e5) 名吃鸡玩家,每名玩家只属于一台服务器。你知道每个玩家的技能值。现在上级命令你从每台服务器挑选一名玩家组建吃鸡梦之队。该队须满足以下两个条件:

设来自 1 号服务器的玩家的技能值为 X1 ,来自 2 号服务器的玩家的技能值为 X2 ,来自 3 号服务器的玩家的技能值为 X3 ,来自 4 号服务器的玩家的技能值为 X4 ,

组队条件是:

组建一只吃鸡梦之队,满足上述条件并且让 d (0 ≤ d) 的值尽可能小。

【思路】显然,必须要让 X1 和 X2 、X3 和 X4 尽可能接近是最优的。

我们先对这四类数进行排序。再不妨设X1<X2,通过枚举X1二分找到X2。再通过X1 - d ≤ X3 ≤X2 + d确定X3,X4即可。

#include <bits/stdc++.h>
#define endl '\n'
#define IO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
int a[100005];
inline void sol(){
    vector<int> v[5]; int n; cin >> n;
    for(int i=1;i<=n;i++) cin >> a[i];
    for(int i=1;i<=n;i++){int x; cin>>x; v[a[i]].push_back(x);}
    for(int i=1;i<=4;i++) sort(v[i].begin(),v[i].end());
    int l=0, r=INT_MAX, ans=0;
    while(l<=r){
        int d = (l+r)>>1; bool flag=0;
        for(auto &x1:v[1]){
            auto pos = lower_bound(v[2].begin(),v[2].end(),x1);
            if(pos==v[2].end()) break;
            
            int x2 = *pos, delta = x2 - d;
            pos = lower_bound(v[3].begin(),v[3].end(),delta);
            if(pos==v[3].end()) continue;
            int x3 = *pos;
            pos = lower_bound(v[4].begin(),v[4].end(),delta);
            if(pos==v[4].end()) continue;
            
            int x4 = *pos;
            if(max(x4,x3)<=x1+d){flag=1; break;}
        }
        for(auto &x2:v[2]){
            auto pos = lower_bound(v[1].begin(),v[1].end(),x2);
            if(pos==v[1].end()) break;
            
            int x1 = *pos, delta = x1 - d;
            pos = lower_bound(v[3].begin(),v[3].end(),delta);
            if(pos==v[3].end()) continue;
            int x3 = *pos;
            pos = lower_bound(v[4].begin(),v[4].end(),delta);
            if(pos==v[4].end()) continue;
            
            int x4 = *pos;
            if(max(x4,x3)<=x2+d){flag=1; break;}
        }
        flag ? ans=d, r=d-1 : l=d+1;
    }
    cout << ans << endl;
}
int main(){
    IO; int ttt; cin >> ttt;
    while(ttt--) sol(); return 0;
}

C题 洞穴探险

【题意】N个点, M 条通路,Q 次询问,第 i 个问题是点 Xi 和点 Yi 之间有没有路径,必须告诉回答没有路径(NONE)、一条路径(ONE)或存在多条路径(MORE THAN ONE)。注意,路径不应多次通过同一个小洞,也不应多次通过同一条隧道。

【思路】以下代码应该是WA + TLE了,回头看看能不能补题,要解决这个数据量的无向图连接问题,应该是要用上并查集(?)

#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long 
#define endl '\n'
using namespace std;
class Graph {
public:
    Graph(int n) : N(n), adj(n) {}
    void addEdge(int x, int y) {
        adj[x].push_back(y);
        adj[y].push_back(x);
    }
    // 深度优先搜索,用于查找从x到y是否有路径
    bool dfs(int start, int target, vector<bool>& visited) {
        if (start == target) return true;
        visited[start] = true;
        for (int node : adj[start]) {
            if (!visited[node]) {
                if (dfs(node, target, visited)) return true;
            }
        }
        return false;
    }
    string ans(int Xi, int Yi) {
        vector<bool> visited(N, false);
        // 初始调用DFS
        bool hasPath = dfs(Xi, Yi, visited);
        if(!hasPath) return "NONE";
        else {
            fill(visited.begin(), visited.end(), false);
            adj[Yi].erase(remove(adj[Yi].begin(), adj[Yi].end(), Xi), adj[Yi].end());
            adj[Xi].erase(remove(adj[Xi].begin(), adj[Xi].end(), Yi), adj[Xi].end());
            if(dfs(Xi, Yi, visited)) return "MORE THAN ONE";
            else return "ONE";
        }
    }
private:
    int N;
    vector<vector<int>> adj;
};
int main() {
    IO; int n, m, q; cin >> n >> m >> q;
    Graph g(n);
    for(int i=0;i<m;i++) {
        int x, y; cin >> x >> y;
        g.addEdge(x,y);
    }
    for(int i=0;i<q;i++) {
        int x, y; cin >> x >> y;
        cout << g.ans(x,y) << endl;
    }
    return 0;
}

D题 火柴棒等式

【题意】这是一个由火柴棒组成的等式(如: 9 - 1 = 4),但它可能不正确。我们需要找到使等式正确所需的最少移动次数(如果没有的话输出-1)。

【思路】枚举,等式由“数字-符号-数字-等于号-数字”组成,所以总情况数为10*2*10*10=2000种。在考虑每个数字最多由7根火柴棒组成,考虑用二进制01串来表述

#include <bits/stdc++.h>
#define ll long long
using namespace std;
map<ll, bool> mp;
// <<0表示上边  <<1表示右上边  <<2表示右下边  <<3表示下边
// <<4表示左下边  <<5表示左上边  <<6表示中边 
const int num[10] = {//分别为0~9 
  (1 << 0)+(1 << 1)+(1 << 2)+(1 << 3)+(1 << 4)+(1 << 5), 
  (1 << 1)+(1 << 2),
  (1 << 0)+(1 << 1)+(1 << 3)+(1 << 4)+(1 << 6),
  (1 << 0)+(1 << 1)+(1 << 2)+(1 << 3)+(1 << 6),
  (1 << 1)+(1 << 2)+(1 << 5)+(1 << 6),
  (1 << 0)+(1 << 2)+(1 << 3)+(1 << 5)+(1 << 6),
  (1 << 0)+(1 << 6)+(1 << 2)+(1 << 3)+ (1 << 4)+(1 << 5),
  (1 << 0)+(1 << 1)+(1 << 2),
  (1 << 0)+(1 << 1)+(1 << 2)+(1 << 3)+(1 << 4)+(1 << 5)+(1 << 6),
  (1 << 0)+(1 << 1)+(1 << 2)+(1 << 3)+(1 << 5)+(1 << 6)
};
ll trans(int a, int op, int b, int c){
    ll encoder = 0;
    encoder += num[a]; encoder <<= 7;
    encoder += num[b]; encoder <<= 7;
    encoder += num[c]; encoder <<= 2;
    if(op==2) return encoder+=3;//(11)2
    else return encoder+=2;    //(10)2
}
void init(){
    for(int x1=0;x1<10;x1++)
        for(int x2=0;x2<10;x2++)
            for(int x3=0;x3<10;x3++)
                for(int op=1;op<=2;op++)
                    if((op==1 && x1-x2==x3)||(op==2 && x1+x2==x3))
                        mp[trans(x1,op,x2,x3)]=1;
}
inline void sol(){
    int a, b, c, op, ans = 999999;
    char ch, useles;
    cin >> a >> ch >> b >> useles >> c;
    op = ch=='+'?2:1;
    ll now = trans(a,op,b,c), cnt1 = __builtin_popcount(now);
    for(auto &x:mp){
    ll tmp = x.first;
        if(__builtin_popcount(tmp)!=cnt1) continue;
        int cnt = 0;
        for(int i=0;i<30;i++){
            if(((now>>i)&1)!=((tmp>>i)&1)) ++cnt;
            ans=min(ans,cnt/2);
    }
    if(ans==999999) puts("-1");
    else cout << ans << endl;
}
int main(){
    int ttt; cin >> ttt; init();
    while (ttt--) sol();
    return 0;
}

E题 排序

【题意】构造一个排列,使得每两个连续数字之间的绝对差的总和 f(p) 最大。如n=5时,排列 4 1 5 2 3 的可取到 f(p) 最大值11。

【思路】贪心,最大最小的数先放一起,让后用双端数组看看是放左边收益大还是右边收益大,最后求和 f(p) 即可。

#include <bits/stdc++.h>
#define ll long long
#define IO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int N =1e5+5;
int a[N],q[N];
void sol(){
    int n; ll sum=0;
    deque<int> a; cin >> n;
    a.push_back(1),a.push_back(n); 
    sum+=abs(n-1);
    int p1=1,p2=n,cnt=0;
    while(p1!=p2){
        ++cnt;int i;
        if(cnt&1) i=(++p1);
        else i=(--p2);
        if(p1==p2) break;
        if(abs(i-a.back())>abs(i-a.front()))
            sum+=abs(i-a.back()),a.push_back(i);
        else sum+=abs(i-a.front()),a.push_front(i);  
    }
    cout <<sum << "\n";
}
int main(){
    IO; int ttt; cin >>ttt;
    while(ttt--) sol();
    return 0;
}

F题 数组查询

【题意】给定一个数组,第i次查询时给一个数Xi,并询问数组前i个元素中小于等于Xi的最大元素。

【思路】采用multiset模拟,STL中set容器的upper_bound(k)函数可以返回一个指向键k之后下一个元素的迭代器。

#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define ll long long
const int N = 1e5+5;
using namespace std;
ll a[N];
inline void sol(){
  int n; cin>>n;
    for(int i=1;i<=n;i++) cin >> a[i];
    multiset<int> s;
    for(int i=1;i<=n;i++){
        int x; cin >> x;
        s.insert(a[i]);
        auto it = s.upper_bound(x);
        if(it==s.begin()) puts("-1");
        else cout << *(--it) << endl;
    }
}
int main(){
    IO; int ttt; cin >> ttt;
    while(ttt--) sol();    
    return 0;
}

G题 新冠病毒

【题意】已知第i天新增i个病例,计算n天内的感染总数和平均感染数。

#include <bits/stdc++.h>
#define ll long long
int main(){
    ll n; std::cin >> n;
    std::cout << n*(n+1)/2 << '\n' << (n+1)/2;
    return 0;
}

H题 有星星就表白

【题意】每次给定一个由 N 个顶点和 M 条边组成的连通无向图(不包含自环或重边),如果是菊花图则输出Yes,否则输出No。

【思路】显然只能有n-1条边,图要联通。然后一个点作为中心,因此只有一个点的度数可以大于2。就做完了。

#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
using namespace std;
const int N =1e5+5;
ll a[N],f[N];
int find(int x){
    return f[x]==x ? x : f[x]=find(f[x]);
}
inline void sol(){
    int n, m; cin >> n >> m;
    for(int i=1;i<=n;i++) f[i]=i,a[i]=0;
    bool flag=1; int cnt=0;
    for(int i=1;i<=m;i++){
        int x, y; cin >> x >> y;
        ++a[x], ++a[y];
        int fx = find(x), fy=find(y); f[fx]=fy;
        if(fx==fy) flag=0;
    }
    if(m+1!=n||!flag){puts("No"); return ;}
    for(int i=1;i<=n;i++)
        if(a[i]>2) cnt++;
    if(cnt<=1)  puts("Yes");
    else puts("No");
}
int main(){
    IO; int ttt; cin >> ttt;
    while(ttt--) sol();
    return 0;
}

I题 笑声比赛

【题意】给定一长度为n的字符串,询问m次,每次询问给出两个字符X和Y和一个长度L,问以XY结尾且长度为L的子序列共有多少个,结果对1e9+7取模。

【思路】动态规划,取字符串最后出现的XY,往前寻找长度为 len - 2 的子序列,用last记录上一个a[i]位置,则状态转移方程如下:(不要忘记取模技巧~)

1、如果last[a[i]] = 0, f[i][j] = f[i-1][j] + f[i-1][j-1];

2、否则f[i][j] = f[i-1][j] + f[i-1][j-1] - f[last[a[i]] - 1][j-1]


#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long 
using namespace std;
const ll N=1e3+5, mod=1e9+7;
ll f[N][N]={{0}}, last[N], pos[27];
inline void sol(){
    int n; cin >> n;
    string s; cin >> s;
    int siz = s.size();
    memset(pos,0,sizeof pos); memset(f,0,sizeof f);
    for(int i = 0; i < siz; i++)
        last[i+1]=pos[s[i]-97], pos[s[i]-97]=i+1;
    f[0][0]=1;
    for(int i=1;i<=n;i++){
      f[i][0]=1;
        for(int j=1;j<=i;j++){
            f[i][j]=(f[i-1][j]+f[i-1][j-1])%mod;
            if(last[i]!=0) f[i][j]=(f[i][j]-f[last[i]-1][j-1]+mod)%mod;
        }
    }
    int q; cin >> q;
    while(q--){
        char x, y; cin >> x >> y;
        int len; cin >> len;
        size_t tmp = s.rfind(y); 
        tmp = s.rfind(x,tmp);
        if((int)tmp<len-2){cout << "0\n"; continue;}
        else cout << f[tmp][len-2]%mod << endl;
    }
}
int main(){
    IO; int ttt; cin >> ttt;
    while(ttt--) sol();
    return 0;
}

J题 行走之谜

【题意】没看懂,放一下原题,估计是不会补了。

全部评论

相关推荐

自由水:笑死了,敢这么面试不敢让别人说
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务