第八届福建省大学生程序设计竞赛-重现赛个人题解

第八届福建省大学生程序设计竞赛-重现赛个人题解



A Frog(签到题)


B Triangles(计算几何)


题意:判断两个三角形是否相交、包含、相离
思路:拿点在多边形内的板子判断一下即可

C DotA and LOL(1e5的网络流?不会做)


D Game(水题)

题意:
Alice 和 Bob 两人分别拥有两个数字\(A,B,A,B<=10^{100000}\),并且都只能对自己的数字进行操作,操作有两种
1.对数字除以10
2.对数字进行翻转
如果Alice可以在任意一个操作后和Bob的数字相等则Alice胜利否则Bob胜利,两人都是使用最优策略。
思路: 首先如果len(B) > len(A),Bob胜
否则,如果B或者翻转后是A的子串,判子串拿KMP或者哈希都可以,Alice胜,反之Bob胜
注意特判一种情况,如果B=0,那么无论怎样都是Alice胜


E Doctor(待补)

题意:
There are n diseases that Kim will suffer from, such as “Healthy”, “Fever”, “Stomache”(You should be careful that in this problem, healthy is also a kind of “disease”).

After a very long period, the doctor discovered that when Kim is suffering from a specific disease, the probability of each response Kim will give to the doctor is fixed.

For example, consider that there are 3 diseases in total, “Healthy”, “Fever” and “Stomache”, and 3 answers in total, “I feel good”, “I feel cold” and “I feel dizzy”. When Kim have a fever, the probability that he will say “I feel good” is 0.3, the probability that he will say “I feel cold” is 0.65 and the probability that he will say “I feel dizzy” is 0.05.

We can rewrite it in mathematics formulas, that is P(I feel good|fever) = 0.3, P(I feel cold|Fever) = 0.65, and P(I feel dizzy|Fever)=0.05.

Besides, the doctor also discovered that when Kim has suffered from a specific disease, next time when he goes to see the doctor, the probability that he is suffering from a specific disease is also fixed.

For example, there’re also 3 diseases in total. On the previous meeting, Kim had a “fever”. Now, on this meeting, the probability that he is “healthy” is 0.3, the probability that he had a “fever” is 0.5, and the probability that he has a “stomache” is 0.2.

We can also rewrite it in mathematics formulas, that is P(Healthy|Fever) = 0.3, P(Fever|Fever) = 0.5, and P(Stomache|Fever)=0.2.

Now, we’ve already known that Kim had seen this doctor k times, and we’ve already known how Kim had responded each time.

To make this problem simpler, we assume that the on the first meeting, the probability of each diseases that Kim may be suffering from is fixed.

Please give me a sequence D of diseases that Di is the disease that Kim was suffering from on the i_th meeting, which satisfied that the probability of this sequence of diseases actually be suffered by Kim is maximum.

  • 大意就是说通过得disease先后的概率不同和回答的概率不同 求一种序列满足概率最大
    看懂题意了就是个简单dp了
    \(dp[i][j]表示第i次得第j种病获得的最大概率\)
    $dp[i][j] = b[j][ask[i]] \cdot \max_{k = 1}^{n}{dp[i-1][k] \cdot a[k][j]} $
    \(其中ask[i]表示第i次的回答,b[i][j]表示得第i种disease回答为j的概率,a[i][j]表示上一次得第i种disease,这一次得j的概率\)
    一直做乘法误差较大,所以取个对数做加法就好了,输出路径开个pa数组记录前驱即可。
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
#include<vector>
#define LL long long
#define P pair<int,int>
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
const int N = 305;
const double eps = 1e-8;
double dp[N][N];
int pa[N][N];
int n, m, k;
double a[N][N],b[N][N],first[N];
int ask[N];
void Print(int cur,int id){
    if(cur != 0){
        Print(cur-1,pa[cur][id]);
    }
    if(cur == 0) printf("%d",id);
    else printf(" %d",id);
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
      scanf("%d%d%d",&n,&m,&k);
      for(int i = 1;i <= n;i++)
        for(int j = 1;j <= n;j++){
                scanf("%lf",&a[i][j]);
                a[i][j] = log(a[i][j]);
        }
      for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++) {
            scanf("%lf",&b[i][j]);
            b[i][j] = log(b[i][j]);
        }
      for(int i = 1;i <= n;i++) {
            scanf("%lf",first + i);
            first[i] = log(first[i]);
      }
      for(int i = 1;i <= k;i++) scanf("%d",ask + i);
      memset(dp,0,sizeof(dp));
      for(int i = 1;i <= n;i++) dp[0][i] = first[i] + b[i][ask[1]];
      for(int i = 1;i < k;i++){
            for(int j = 1;j <= n;j++){
                int ix = 1;
                for(int p = 1;p <= n;p++){
                    double res = dp[i-1][p] + a[p][j];
                    if(res - dp[i-1][ix] - a[ix][j] > eps){
                        ix = p;
                    }
                }
                dp[i][j] = dp[i-1][ix] + a[ix][j] +  b[j][ask[i+1]];
                pa[i][j] = ix;
            }
      }
      int id = 1;
      for(int i = 2;i <= n;i++) if(dp[k-1][i] - dp[k-1][id] > eps) id = i;
      Print(k-1,id);
      printf("\n");
    }
    return 0;
}

F Change(树状数组)

题意:一棵以1为根节点的树,初始的时候全部节点权值为0,有Q个操作,操作如果如下:
1 v x k 对于以v为根节点的子树,a[v]+=x, a[v']+=x-k,a[v'']+=x-2k等等,其中v'是v的孩子,v''是v'的孩子,依次类推
2 v 查询a[v] % 1000000007的值

思路:首先知道对于一个点,只有本身或者其祖先对其有影响
\(用delta[i]表示所有针对结点i的1操作所叠加的-k\)
\(用x[i]表示所有针对结点i的1操作所叠加的x\)
\(于是ans[v] = x[v] + x[pa(1)] + x[pa(2)] + .. + 1 * delta[pa(1)] + 2 * delta[pa(2)] ...\)
\(这里pa(i)表示v的第i级祖先\)
转化为一下
\(ans[v] = x[v] + x[pa(1)] + x[pa(2)] + ... + (dep[v] - dep[pa(1)]) * delta[pa(1)] + (dep[v] - dep[pa(2)]) * delta[pa(2)] + ...\)
\(ans[v] = x[v] + x[pa(1)] + x[pa(2)] + ... + dep[v] * (delta[pa(1)] + delta[pa(2)] + ...) \\ - dep[pa(1)] * delta[pa(1)] + dep[pa(2)] * delta[pa(2)] - ....\)

看到这里就清楚了

\(令predelta[i]表示根到i路径上所有delta的和\)
\(prex[i]表示根到i路径上所有x的和\)
\(predd[i]表示根到i路径上所有delta*对应深度的和\)
\(ans[i] = prex[i] + dep[i] * predelta[i] - predd[i]\)

所以对于操作1,更新结点v,显然以v为根的子树的所有结点都要做相应更新
子树问题对应dfs序列的区间更新
由于这里只是给区间统一变化一个数,用L处+x,R+1处-x,求前缀和即可,树状数组即可。开始也写了线段树,式子没有处理好,我写了两颗线段树,所以超时了,改成树状数组的做法简直快的飞起

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<vector>
#define LL long long
#define P pair<int,int>
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
const int mod = 1e9 + 7;
const int N = 3e5 + 10;
const int M = (N<<2);
inline int read(){
    int x = 0;
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar();
    return x;
}
inline void add(int &x,int y){
    x += y;
    if(x >= mod) x -= mod;
    if(x < 0) x += mod;
}
int fa[N];
vector<int> G[N];
int tot,n,q;
int L[N],R[N],dep[N];
int lowbit(int x){
    return x & (-x);
}
int s[3][N];
void up(int i,int pos,int val){
    while(pos <= n){
        add(s[i][pos],val);
        pos += lowbit(pos);
    }
}
int getsum(int i,int pos){
    int ans = 0;
    while(pos){
        add(ans,s[i][pos]);
        pos -= lowbit(pos);
    }
    return ans;
}
void update(int v,int x,int k){
    int l = L[v],r = R[v],d = 1LL * dep[v] * k % mod;
    up(0,l,x);
    up(0,r+1,-x);
    up(1,l+1,-k);
    up(1,r+1,k);
    up(2,l+1,d);
    up(2,r+1,-d);
}
int query(int v){
    int pos = L[v],d = dep[v];
    int ans = getsum(0,pos);
    add(ans,1LL * d * getsum(1,pos) % mod);
    add(ans,getsum(2,pos));
    return ans;
}
void init(){
    for(int i = 1;i <= n;i++) G[i].clear();
    for(int i = 0;i < 3;i++)
        for(int j = 1;j <= n;j++) s[i][j] = 0;
    tot = 0;
}
void dfs(int u,int d){
    L[u] = ++tot,dep[u] = d;
    for(int i = 0;i < (int)G[u].size();i++){
        dfs(G[u][i],d + 1);
    }
    R[u] = tot;
}
int main()
{
    int T;
    T = read();
    while(T--)
    {
       n = read();
       init();
       for(int i = 2;i <= n;i++){
        fa[i] = read();
        G[fa[i]].push_back(i);
       }
       dfs(1,1);
       q = read();
       int op,v,x,k;
       while(q--){
        op = read(),v = read();
        if(op == 1){
            x = read(),k = read();
            update(v,x,k);
        }else{
            printf("%d\n",query(v));
        }
       }
    }
    return 0;
}

G YYS(概率dp+高精度)

题意:有n张卡片,你每次可以从n张卡片(有放回)里面选择一张,费用为W=(n-1)!
问你选择过这n种卡片的费用的期望
思路:概率dp
\(定义dp[i]表示已经选择了i种卡片,还需要选择n-i种卡片的费用的期望\)
\(显然dp[n] = 0,考虑每次选择的卡片是否会导致种类增加\)
\(对于任意0<=i<n则有\)
\(dp[i] = \frac{(n-i)}{n} \cdot (dp[i+1]+W)+\frac{i}{n} \cdot (dp[i]+W)\)
\(化简得dp[i] = dp[i+1] + \frac{nW}{n - i}\)
于是\(ans = dp[0] = \sum{\frac{nW}{i}}(1<=i<=n)\)
n<=3000,高精度拿java大数做
java不熟练试了好多发

import java.util.Scanner;
import java.math.*;
  
public class Main {
     
    public static void main(String[] args) {
        BigInteger ans;
        int T,n;
        Scanner in = new Scanner(System.in);
        T = in.nextInt();
        for (int i = 1; i <= T; ++i) {
           n = in.nextInt();
           ans = BigInteger.valueOf(1);
           for(int j = 1;j <= n;j++) ans = ans.multiply(BigInteger.valueOf(j));
           BigInteger tmp = BigInteger.valueOf(0);
           for(int j = 1;j <= n;j++) tmp = tmp.add(ans.divide(BigInteger.valueOf(j)));
           System.out.println(tmp+".0");
        }
    }
}
 

## H Cantonese(不会做)

I Magic(字典树+树状数组)

题意:给n个长度<=1000的字符串Si,每个字符串有权值w[i]
有两种操作
2 k 选中第k个字符串,问你有多少个字符串满足w[i]<=w[k]而且S[k]是S[i]的后缀
1 k y 把第k个字符串的权值修改为y

思路:把字符串反转就转化为前缀的问题,这样就可以用字典树来处理是否为前缀问题
然后再用树状数组来维护数量
具体做法是把字符串按长度从小到大插入字典树
对于第i次插入, 若某个结点是结尾结点,则将该点的树状数组更新
因为以该点结尾的字符串是当前插入字符串的前缀
查询操作就是查询第k个字符串结尾的点的树状数组1到w[k]的和
修改操作同样和插入操作类似,把路径上结尾结点的所有树状数组更新
由于只有n个字符串,开n颗树状数组就够了
查询操作O(logN)
修改操作O(len logN)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<set>
#include<queue>
#define LL long long
#define P pair<int,int>
using namespace std;
const int M = 1e5 + 10;
const int N = 1e3 + 10;
char s[N][N];
int n,q;
int w[N];
int ch[M][26],tot;
int has[M * 26],id;
int len[N],ran[N],End[N];
int sum[N][N];
bool cmp(int x,int y){
    return len[x] < len[y];
}
void init(){
    id = tot = 0;
    memset(ch[0],0,sizeof(ch[0]));
    memset(sum,0,sizeof(sum));
    memset(has,0,sizeof(has));
}
int lowbit(int x){
    return x & (-x);
}
void up(int i,int pos,int val){
    while(pos <= 1000){
        sum[i][pos] += val;
        pos += lowbit(pos);
    }
}
int getsum(int i,int pos){
    int ans = 0;
    while(pos){
        ans += sum[i][pos];
        pos -= lowbit(pos);
    }
    return ans;
}
int ix(char c){
    return c - 'a';
}
void Insert(char *s,int v){
    int o = 0,c;
    for(int i = len[v] - 1;i >= 0;i--){
        c = ix(s[i]);
        if(!ch[o][c]){
            memset(ch[++tot],0,sizeof(ch[tot]));
            ch[o][c] = tot;
        }
        o = ch[o][c];
        if(has[o]) up(has[o],w[v],1);
    }
    if(!has[o]){
        has[o] = ++id;
        up(has[o],w[v],1);
    }
    End[v] = has[o];
}
void update(int v,char *s,int y){
    int o = 0,c;
    for(int i = len[v] - 1;i >= 0;i--){
        c = ix(s[i]);
        o = ch[o][c];
        if(has[o]){
            up(has[o],w[v],-1);
            up(has[o],y,1);
        }
    }
    w[v] = y;
}
int main()
{
    int T;
    cin>>T;
    while(T--){
        scanf("%d",&n);
        for(int i = 1;i <= n;i++){
            scanf("%s%d",s[i],&w[i]);
            ran[i] = i;
            len[i] = strlen(s[i]);
        }
        init();
        sort(ran+1,ran+n+1,cmp);
        for(int i = 1;i <= n;i++){
            int v = ran[i];
            Insert(s[v],v);
        }
        scanf("%d",&q);
        int op,x,y;
        while(q--){
            scanf("%d%d",&op,&x);
            if(op == 1){
                scanf("%d",&y);
                update(x,s[x],y);
            }else printf("%d\n",getsum(End[x],w[x]));
        }
    }
    return 0;
}

J Trades(贪心+高精度)

题意:你有m元,0张显卡,有一些显卡在接下来n天的单价为Ci
在接下来的n天里,你可以选择买或者卖显卡,问你最后手里最多有多少钱
思路:贪心,要买就尽可能的多买,在递增的情况下买卖即可

import java.util.*;
import java.math.BigInteger;

public class Main {
    public static void main(String[] args){
        int T, n, m;
        BigInteger mod = BigInteger.valueOf(1000000007);
        Scanner in = new Scanner(System.in);
        T = in.nextInt();
        int C[] = new int[2010];
        C[0] = 0;
        for(int cas = 1; cas <= T;cas++){
            n = in.nextInt();
            m = in.nextInt();
            int last = 1;
            BigInteger ans = BigInteger.valueOf(m);
            for(int i = 1;i <= n;i++){
                C[i] = in.nextInt();
                if(C[i-1] > C[last] && C[i] < C[i-1]){
                    BigInteger cnt = ans.divide(BigInteger.valueOf(C[last]));
                    ans = ans.subtract(cnt.multiply(BigInteger.valueOf(C[last]))).
                            add(cnt.multiply(BigInteger.valueOf(C[i-1])));
                    last = i;
                }
                else if(C[i] < C[i-1]) last = i;
            }
            if(C[n] > C[last]){
                BigInteger cnt = ans.divide(BigInteger.valueOf(C[last]));
                ans = ans.subtract(cnt.multiply(BigInteger.valueOf(C[last]))).
                        add(cnt.multiply(BigInteger.valueOf(C[n])));
            }
            System.out.println("Case #"+cas+": "+ans.mod(mod));
        }
    }
}

K Wand(组合数学)

题意:长度为n的全排列中至少排对了k个位置的排列有多少
思路:从n个数选k个排对,其余数完全错排
错排公式长度为i的完全错排方案数为f[i] = (i-1)(f[i-1]+f[i-2])(i>=3)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <string>
#include <set>
#include <stack>
#include <map>
#include <queue>
using namespace std;
typedef long long LL;
const LL mod=1e9+7+0.5;
LL const maxn=10100, maxk=110;
LL cu[maxn];
int n,k;
void init()//预处理求错排
{
    cu[0]=1; cu[1]=0;
    for (LL i=2; i<maxn; i++){
        cu[i]=(i-1)*(cu[i-1]+cu[i-2])%mod;
    }
}
void ext_gcd(LL a, LL b, LL &d, LL &x, LL &y)//拓展欧几里得求逆元
{
    if (!b){
        d=a; x=1; y=0;
    }
    else{
        ext_gcd(b, a%b, d, y, x);
        y-=x*(a/b);
    }
}
LL inv(LL k)//求逆元
{
    LL a=k, b=mod, x, y, d;
    ext_gcd(a, b, d, x, y);
    return (x%mod+mod)%mod;
}
LL c[maxn];
void C(LL n, LL k)//递推求组合数
{
    c[0]=1;
    if (k==n) c[n]=1;
    for (LL i=1; i<=k; i++){
        if (i>(n+1)/2) c[i]=c[n-i];
        c[i]=c[i-1]*(n-i+1)%mod*inv(i)%mod;
    }
}
int main()
{
    init();
    int T;
    for (scanf("%d", &T); T; T--){
        scanf("%d%d", &n, &k);
        LL ans=0;
        C(n, n-k);
        for (int i=0; i<=n-k; i++){
            ans=(ans+c[i]*cu[i])%mod;
        }
        printf("%d\n", (int)(ans%mod));
    }
    return 0;
}

L Tic-Tac-Toe(模拟)

题意:3*3的格子,下三子棋,给一个当前局面,问是否在两步以内获胜
思路:暴力三步 或者 暴力一步判断是否有两种赢的方法

转载于:https://www.cnblogs.com/jiachinzhao/p/7226435.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值