即 重现 2019 ICPC North American Qualifier Contest

A.Circuit Math 【模拟】
题意: 给出每个变量的真值, 问逻辑电路最后的输出结果。
数据结构后缀表达式求值, 借助栈来实现, 最后栈中的元素就是答案。
【我用30存了一个T, 31存了一个F, 方便写】

#include<bits/stdc++.h>

using namespace std;

const int N = 50;

bool book[N];
int n;
char c;

int main(){
    cin >> n;
    book[30] = 1, book[31] = 0;
    for(int i = 1; i <= n; ++ i){
        scanf(" %c", &c);
        if(c == 'T')    book[i] = 1;
        else    book[i] = 0;
    }
    stack<int> s;    getchar();
    while((c = getchar()) != EOF){
        if(c == ' ')    continue;
        //printf("c = %c\n", c);
        if(isalpha(c))    s.push(c - 'A' + 1);
        else if(c == '*'){
            int t1 = s.top(); s.pop();
            int t2 = s.top(); s.pop();
            //printf("t1 = %d t2 = %d\n", t1, t2);
            int ans;
            if(book[t1] && book[t2])    ans = 1;
            else    ans = 0;
            s.push(ans == 1 ? 30 : 31);
        //    printf("ans = %d\n", ans);
        }else if(c == '+'){
            int t1 = s.top(); s.pop();
            int t2 = s.top(); s.pop();
            //printf("t1 = %d t2 = %d\n", t1, t2);
            int ans;
            if(!book[t1] && !book[t2])    ans = 0;
            else    ans = 1;
            //printf("ans = %d\n", ans);
            s.push(ans == 1 ? 30 : 31);
        }else if(c == '-'){
            int t = s.top();     s.pop();
            int ans = book[t];
            s.push(ans == 1 ? 31 : 30);
            //printf("ans = %d\n", ans);
        }
    }
    cout << (book[s.top()] == 1 ? "T" : "F");
    return 0;
}

B. Diagonal Cut 【思维】
题意:给一个M * N的蛋糕, 从左下-右上对角线切一刀, 问有多少个小方格面积被恰好1分为2.
思路:对角线的斜率为N/M. 如果一个小正方形的中心在对角线上,那一定能被一分为二。所以转化成如何求有多少小正方形的中心在对角线。
在直角坐标系中,(i, j)的正方形中心坐标是(i-1/2, j-1/2),由斜率可以化简得到:
2i-1/2j-1 = N/M. 所以必须保证N M都是奇数才有解, 否则就是无解。(因为左边的分子分母都是奇数,右边已经约分过,不可能是两个偶数,只有都是奇数才会有解), 解的数量就是gcd(N,M).

#include<bits/stdc++.h>
#define ll  long long 
using namespace std;

ll n, m;

ll gcd(ll a, ll b){
    return b ? gcd(b, a % b) : a;
}

int main(){
    cin >> n >> m;
    ll g = gcd(n, m);
    n /= g, m /= g;
    if(n % 2 == 0 || m % 2 == 0)    cout << 0;
    else cout << g;
    return 0;
}

C.Gerrymandering【阅读理解, 模拟】
题意: 输出每行的第一个数表示选举票数最多的人(A或B),第二个数表示A 浪费的票数,第三个数表示B 浪费的
票数。最后一个小数,应该将A 在各个地区获得的总票数与B 在各个地区的总票数的差值,将这个差值
除以 A和B 所有的总票数。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 1010;

int p, d, wasteA, wasteB, total;

struct node{
    int A, B, wa, wb;
}book[N];

int main(){
    cin >> p >> d;
    while(p --){
        int x, a, b;
        cin >> x >> a >> b;
        book[x].A += a;
        book[x].B += b;
    }
    for(int i = 1; i <= d; ++ i){
        int tmp = (book[i].A + book[i].B) / 2 + 1;
        if(book[i].A > book[i].B)    book[i].wa =  book[i].A - tmp, book[i].wb = book[i].B;
        else    book[i].wb =  book[i].B - tmp, book[i].wa = book[i].A;
        total += book[i].A + book[i].B;
        wasteA += book[i].wa;
        wasteB += book[i].wb;
    }
    for(int i = 1; i <= d; ++ i){
        printf("%c %d %d\n", (book[i].A > book[i].B ? 'A' : 'B'), book[i].wa, book[i].wb);
    }
    printf("%.10lf", (double)abs(wasteA - wasteB) / total);
    return 0;
} 

D. Missing Numbers【签到】
题意: 问从1到最大的数中没出现的数,并输出。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 1010;

int a[N], n, maxx;

int main(){
    cin >> n;
    bool f = 0;
    for(int i = 1; i <= n; ++ i){
        int x;
        cin >>x;
        a[x] = 1;
        maxx = max(maxx, x);
    }
    for(int i = 1; i <= maxx; ++ i){
        if(!a[i])    f = 1, cout << i << endl;
    }
    if(!f)    puts("good job");
    return 0;
} 

I. Slow Leak【思维,建图方式, 最短路的综合应用】
太妙了太妙了wwww
题意:1号起点,n号终点。自行车车胎损坏,加满气只能最多走d公里。 路上某些点是加气站, 可以把气冲满。初始时气是满的,问能否到达终点以及能到的最短路。
思路:这个题是没法直接用dijkstra在里面修改的, 因为并不是边权是第一排序关键字, 气是第二关键字。有的时候会故意走远路为了加气。
所以先用flyod预处理每个点之间的最短路(不考虑气的问题), 然后重新建图,只保留终点、起点以及所有的加气站,边是这些点之间的边,边权是他们之间的最短路(为了方便处理, 大于d的边可以不建)。然后在新图上跑一遍dijkstra求最短路即可。【因为新图全是加气站,就不用考虑加气的问题了,每到一个点就把气加满】。 思路还是tql
tips: 这题会爆int, 所以初始化mp dis的时候不能用0x3f(太小了)

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 510, M = 250010;
const ll inf = 1e18;

ll mp[N][N],dis[N],w[M],d;
int n,m,G[N],q;
int idx, ne[M], e[M], h[N];
bool st[N];

void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;} 

struct node{
    int u; ll d;
    bool operator < (const node& rhs) const{
        return d > rhs.d;
    }
};

void flyod(){
    for(int k = 1; k <= n; ++ k)
        for(int i = 1; i <= n; ++ i)
            for(int j = 1; j <= n; ++ j)
                if(mp[i][j] > mp[i][k] + mp[k][j])
                    mp[i][j] = mp[i][k] + mp[k][j];
}

void dijkstra(int u){
    for(int i = 1; i <= n; ++ i)    dis[i] = inf;
    dis[u] = 0;
    priority_queue<node> q;
    q.push(node{u, 0});
    while(!q.empty()){
        node fr = q.top(); q.pop();
        int u = fr.u;
        if(st[fr.u])    continue;
        st[fr.u] = true;

        for(int i = h[u]; ~i; i = ne[i]){
            int j = e[i];
            if(dis[j] > dis[u] + w[i]){
                dis[j] = dis[u] + w[i];
                q.push(node{j, dis[j]});
            }
        }
    }
    if(dis[n] == inf)    puts("stuck");
    else     cout << dis[n];
}

int main(){
    cin >> n >> m >> q >> d;
    for(int i = 1; i <= n; ++ i)
        for(int j = 1; j <= n; ++ j)
            mp[i][j] = (i == j ? 0 : inf);
    for(int i = 1; i <= q; ++ i){
        int x;
        cin >> x;
        G[x] = 1;
    }
    for(int i = 1; i <= m; ++ i){
        int a, b, c;
        cin >> a >> b >> c;
        mp[a][b] = mp[b][a] = c;
    }
    flyod();
    memset(h, -1, sizeof h);
    for(int i = 1; i <= n; ++ i){
        if(i == 1 || i == n || G[i]){
            for(int j = i + 1; j <= n; ++ j){
                if(mp[i][j] > d)    continue ;
                if(G[j] || j == n)    add(i, j, mp[i][j]), add(j, i, mp[i][j]);
            }
        }
    }
    dijkstra(1);
    return 0;    
}

J. Stop Counting!【前缀和 思维】
题意:给一个数组,可以选择去掉其中一段连续的序列(可以全部去掉),使得剩余部分的平均值最大。
思路:求一遍前缀和,同时维护最大pre_avg, 再反向求一遍, 维护一个sub_avg。 两者最大的就是答案(如果都比0小,就是0,全删掉)。

可以贪心的解: 假如去掉中间的C段, 留下两边的A和B:
A C B, 此时, 如果A比B大,那会连同B一起删掉; 反之亦然。所以不可能只删掉中间的某一段而留下后面的【可以认为A B两段除非平均值相等, 否则小的一定会拖累大的,不如删掉; 而平均值相等的话,没任何影响。】

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 1000010;

ll a[N],n,sum1,sum2;
double ave, minn = 1e18, maxa = 0, maxb = 0;

int main(){
    cin >> n;
    for(int i = 1; i <= n; ++ i){
        scanf("%lld", &a[i]);
        sum1 += a[i];
        ave = (double)sum1 / i;
        maxa = max(maxa, ave);
    }
    for(int i = n; i >= 1; -- i){
        sum2 += a[i];
        ave = (double)sum2 / (n - i + 1);
        maxb = max(maxb, ave);
    }
    printf("%.10lf", max(maxa, maxb));
    return 0;
}

K. Summer Trip【复杂度估计, 思维, 暴力?】
题意:从一个只有大写字母的字符串中求得符合以下要求的字符串数量:
1.字符串中至少含有2个不同字母;
2.连续的一段
3.首尾字符在选出的子串中只出现一次
思路:会想到一个O(N2)的暴力, 但是1e5肯定会超时。 可以发现,如果固定起点的话,只要向后遍历到一个和起点相同的字母,就可以停止匹配(因为之后的字符串首字母一定出现了>=2次了)。因为只有大写, 所以最坏情况也只会向后遍历26次, 所以复杂度实际是O(26n).

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 100010;

int n,ans;
char s[N];

int main(){
    scanf("%s", s + 1);
    n = strlen(s + 1);
    for(int i = 1; i <= n; ++ i){
        int book[30] = {0};
        book[s[i] - 'a'] = true;
        for(int j = i + 1; j <= n; ++ j){
            if(s[j] == s[i])    break;
            if(!book[s[j] - 'a'])    ans ++;
            book[s[j] - 'a'] = true;
        }
    }
    cout << ans;
    return 0;
}

M. Zipline【数学, 二分, 求导】
题意:看图说话8
思路:求出绳子长度的表达式,求导,当导数为0的时候最大。
所以可以二分找导函数的零点。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 100010;

int t;
double w,g,h,r,eps = 1e-6;

double f(double x){
    return sqrt(x * x + (g - r) * (g - r)) + sqrt((h - r) * (h - r) + (w - x) * (w - x));
}

double ff(double x){
    return x / sqrt(x * x + (g - r) * (g - r)) + (x - w) / sqrt((h - r) * (h - r) + (w - x) * (w - x));    
}

void solve(){
    cin >> w >> g >> h >> r;
    double l = 0, r = w, mid = (l + r) / 2, last = ff(mid);
    while(abs(last) > eps){
        if(last < 0)    l = mid; 
        else    r = mid;
        mid = (l + r) / 2;
        last = ff(mid);
    }    
    printf("%.8lf %.8lf\n",sqrt(w * w + abs(h - g) * abs(h - g)), f(mid));
}

int main(){
    cin >> t;
    while(t --)    solve();
    return 0;
}

H.Running Routes【思维, 区间dp】
太妙了wwwww 一个方程很基础的区间Dp, 但是比赛时过得很少, 因为思路不是很好想。
定义dp[i,j]为从i到j这个编号区间中能选择的跑道的最大数目。那么分界点为k, 如果以这个为分界线的话,可以分成(i+1,k-1)|(k+1,j)【保证点不能相交】两个区域,这两个区域只能在彼此的区域内选点【否则就交叉了】, 这样就可以进行区间DP的转移了。
tips:注意第三重循环中k 要从i开始, j结束(闭区间)【这样包括了i点不选和j点不选这两种情况】

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 510;

int mp[N][N], dp[N][N], n, m, ans;

int main(){
    //freopen("5.txt", "r", stdin);
    cin >> n;
    for(int i = 1; i <= n; ++ i)
        for(int j = 1; j <= n; ++ j)
            cin >> mp[i][j];
    for(int len = 1; len < n; ++ len)
        for(int i = 1; i + len <= n; ++ i){
            int j = i + len;
            for(int k = i; k <= j; ++ k)
                dp[i][j] = max(dp[i][j], dp[i + 1][k - 1] + dp[k + 1][j] + mp[i][k]);
        }

    cout << dp[1][n];
    return 0;
}

G. Research Productivity Index【概率dp】
题意:小明手里有n份代码,每一份代码都有一个ac成功率。f值等于a^{a/b},其中a为ac的数量,b为总提交数,比如交了5份4份ac, f = 4^{4/5}≈3.031433 , 最后问交几份怎么交期望值最大。
思路:dp[i,j]表示前i份中通过j份的概率, 可以递推得到。
要特别注意init的时候dp[0,0]=1, 表示前0份过0份的概率是1, 然后dp[i][i] 要特殊判断。
其余的dp[i,j] = dp[i-1,j] * 当前不通过的概率 + dp[i-1,j-1] * 当前通过的概率。
预处理dp数组后,枚举交i份过j份的得分, 实时更新ans即可。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 110;

int n,a[N];
double p[N],ans,dp[N][N];

int main(){
    cin >> n;
    for(int i = 1; i <= n; ++ i)    cin >> a[i];
    sort(a + 1, a + 1 + n, greater<int>());
    for(int i = 1; i <= n; ++ i)    p[i] = 1.0 * a[i] / 100;        //预处理
    dp[0][0] = 1;
    for(int i = 1; i <= n; ++ i){
        dp[i][0] = dp[i - 1][0] * (1 - p[i]);
        for(int j = 1; j < i; ++ j)
            dp[i][j] = dp[i - 1][j - 1] * p[i] + dp[i - 1][j] * (1 - p[i]);
        dp[i][i] = dp[i - 1][i - 1] * p[i];
    }
    for(int i = 1; i <= n; ++ i){
        double res = 0;
        for(int j = 1; j <= i; ++ j)    
            res += pow(j, 1.0 * j / i) * dp[i][j];
        ans = max(ans, res);
    }
    printf("%.10lf", ans);
    return 0;         
}