2022牛客蔚来杯第五场KBHDCG

2022牛客蔚来杯第五场

K.Headphones

  • 题意

    • 总共有n对不同的耳机,已经拿出k对
    • 问至少需要再拿多少只耳机才能从n-k对中取出k+1对耳机
  • 题解

    • 签到题
    • 如果剩余的n-k对已经不足以凑出k+1对,那么一定取不出k+1对耳机
    • 如果剩余的n-k对数量上足够凑出k+1对,那么考虑取多少只耳机才能保证一定能取出k+1对耳机。剩余2*(n-k)只耳机,最坏情况为把所有的单只耳机取出来了,即取了n-k只耳机,根据鸽巢原理只需再取k+1只耳机一定能保证取出k+1对耳机。即总共需取(n-k)+(k+1)=n+1只耳机
  • 代码

#include <iostream>

using namespace std;

int main() {
    int n,k;
    cin>>n>>k;
    if(n-k<k+1) cout<<-1<<'\n';
    else cout<<n+1<<'\n';
    return 0;
}

B.Watches

  • 题意

    • 给n个表,给定每个表的价格,有m元钱
    • 买表的价格为,手表本身的价钱+购买表的数量*表原始给的顺序i
    • 问最多可以买多少个手表
  • 题解

    • 研究对象是手表的时候,不能确定哪些价值更小,因为k是不确定的
    • 研购买究数量k时,k对买表价格的影响是单调的,所以可以二分k。k确定的情况下所有表的价格确定,直接算出价格排序后check前k个能不能买下来即可。
  • 代码

#include <iostream>
#include <algorithm>

using namespace std;
#define int long long
const int N=1e5+5;

int n,m;
int a[N],b[N];//注意开两个数组,一个存原价值,一个用来check时计算新价值排序的,因为需要check多次,所以只用一个数组不行

bool check(int k) {
    for(int i=1;i<=n;i++) b[i]=a[i]+k*i;//计算新价值
    sort(b+1,b+1+n);//排序
    
    int res=0;
    for(int i=1;i<=k;i++) {//检验是否能买下
        res+=b[i];
        if(res>m) return false;
    }
    return true;
}

void solve() {
    for(int i=1;i<=n;i++) cin>>a[i];
    
    int l=0,r=n;
    while(l<r) {//二分k
        int mid=(l+r+1)>>1;
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    cout<<r<<'\n';
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>n>>m) solve();
    
    return 0;
}

H.Cutting Papers

  • 题意

    • 求下两式图形的并集面积
      { ∣ x ∣ + ∣ y ∣ + ∣ x + y ∣ < = n x 2 + y 2 = n 2 4 \begin{cases} |x|+|y|+|x+y|<=n\\ x^2+y^2=\frac{n^2}{4} \end{cases} {x+y+x+y<=nx2+y2=4n2
  • 题解

    • 如图,为半个圆+两个小正方形的面积
  • 代码

#include<bits/stdc++.h>
using namespace std;
const double pi=3.1415926535897932385;
//可以写成 pi=acos(-1);
int main() {
    long long n;
    while(cin>>n)
        printf("%.12f\n", n * n * pi / 8.0 + n * n / 2.0);
    return 0;
}

D.Birds in the tree

  • 题意

    • 给01字符串表示一棵树所有节点的颜色,以及树的边
    • 求有多少个连通子图使得度为1的节点颜色相同
  • 题解

    • 树形dp

    • f[u,0/1]表示以u为根的子树,u在联通块内,联通块内度数为1的颜色为0/1的联通块数量

    • 假设u点颜色为0(1一样)
      f [ u , 0 ] = ∏ v ∈ c h i l d u ( f [ v , 0 ] + 1 ) f [ u , 1 ] = ∏ v ∈ c h i l e u ( f [ v , 1 ] + 1 ) − ∑ v ∈ c h i l d u f [ v , 1 ] − 1 f[u,0]=\prod_{v\in child_{u}}(f[v,0]+1)\\ f[u,1]=\prod_{v\in chile_{u}}(f[v,1]+1)-\sum_{v\in child_{u}}f[v,1]-1 f[u,0]=vchildu(f[v,0]+1)f[u,1]=vchileu(f[v,1]+1)vchilduf[v,1]1

    • 状态转移方程解释:

      f[u,0],对于根节点u的所有孩子都可以选或者不选,即每个孩子对答案的贡献为f[v,0]+1,由乘法原理可以得到上式。并且因为u的颜色为0,所以即使所有孩子都不选联通块只剩u这一个1度的节点也是符合要求的。

      f[u,1],一样对于孩子节点可以选和不选然后乘法原理,但是要减去只剩自己的情况,因为颜色不对。同时排除有根节点只有1度的情况,因为根节点不符合颜色要求,所以-1*f[v,1],(排除如下图所示的情况)

    • a n s = ∑ i = 1 n ( f [ i , 1 ] + f [ i , 0 ] ) ,从底向上算, d f s 时实则是这样算 ans=\sum_{i=1}^{n}(f[i,1]+f[i,0]),从底向上算,dfs时实则是这样算 ans=i=1n(f[i,1]+f[i,0]),从底向上算,dfs时实则是这样算

  • 代码

#include <iostream>
#include <cstring>

using namespace std;
const int N=3e5+10,mod=1e9+7;
typedef long long LL;

char s[N];
int n,pos[N];
int h[N],e[2*N],ne[2*N],idx;//注意无向边的要开两倍范围
LL ans,f[N][2];//答案,动态数组

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

void dfs(int u,int fa) {//因为加边会加入父亲节点,而题目要求的是子树,所以传入父节点用来排除计算了父节点
    f[u][0]=f[u][1]=1;//初始化为1,才能计算
    LL sum=0;
    for(int i=h[u];~i;i=ne[i]) {//遍历所有子节点
        int j=e[i];
        if(j!=fa) {
            dfs(j,u);//递归计算
            sum=(sum+f[j][pos[u]^1])%mod;
            f[u][0]=(f[u][0]*(1+f[j][0]))%mod;//乘法原理
            f[u][1]=(f[u][1]*(1+f[j][1]))%mod;
        }
    }
    f[u][pos[u]^1]--;//因为一开始预支了1给这个不符合的,所以再减回来
    ans=(ans+f[u][0]+f[u][1]-sum+ mod) % mod;//因为上面-1过,回溯回去计算的时候,相当于u这个子节点不选的方案数已经减掉了,所以不需要f[u][pos[u]^1]-sum,但是ans是全局答案,所以需要再减sum
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin>>n;cin>>s+1;
    for(int i=1;i<=n;i++) pos[i]=s[i]-'0';//记录每个点的颜色
    int a,b;
    memset(h,-1,sizeof h);//初始化表头
    for(int i=1;i<n;i++) {//加边
        cin>>a>>b;
        add(a,b);add(b,a);//无向边
    }
    
    dfs(1,1);//从根开始算
    cout<<ans<<'\n';
    
    return 0;
}

C.Bit Transmission

  • 题意

    • 一个长度为n的01字符串,询问3n次问某个位置是否为1并得到答案
    • 回答中最多会出现一次错误的答案,问能否确定这个字符串
  • 题解

    • 对每一个位置考虑能否确定字符是什么,对于某位的YES和NO的数量分类讨论

      N\Y数量01>1
      0-11/-11
      10/-1-11
      >100-1
      1. 可确定字符四种情况

        Y=1,N>1;确定yes是说谎,所以字符为’0’

        Y>1,N=1;同上,字符为’1’

        Y>1,N=0;一定没有谎,所以答案为’1’

        Y=0,N>1;同上,字符为‘0’

      2. 不可确定字符的三种情况

        Y=0,N=0;没有任何信息,不可能确定

        Y=1,N=1;说谎了,不知道是哪个,不可能确定

        Y>1,N>1;谎言数量>1了,不符合题意,无法确定

      3. 可能可以确定的两种情况

        Y=1,N=0;如果所有位置判断完之后说有过谎,那么可以确定这位字符为’1’

        Y=0,N=1;同理

        这种情况没办法在线判断,只能完全判断之后再来判断。所以可以先认为能判断字符是什么,同时记录这种情况有没有出现,最终如果(没说过谎言&&出现过这种情况),无法确定字符

  • 代码

#include <bits/stdc++.h>
 
using namespace std;
const int N=1e5+10;

struct bit {
    int y, n;
}a[N];
 
bool ans[N];
 
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    int x;
    string s;
    for (int i = 1;i <= 3 * n;i++) {
        cin >> x >> s;
        if (s == "YES") a[x].y++;
        else a[x].n++;
    }
    
    bool fault = false,have=false;
    bool ok = true;
    for (int i = 0;i < n;i++) {
        if (a[i].y > 1 && a[i].n == 0) ans[i] = 1;
        else if (a[i].y == 0 && a[i].n > 1) ans[i] = 0;
        else if (!fault && a[i].y > 1 && a[i].n == 1) ans[i] = 1, fault = 1;
        else if (!fault && a[i].y == 1 && a[i].n > 1) ans[i] = 0, fault = 1;
        else if (a[i].y == 1 && a[i].n == 0) ans[i] = 1,have=true;
        else if (a[i].y == 0 && a[i].n == 1) ans[i] = 0,have=true;
        else ok = false;
    }
    if (!ok || !fault&&have) cout << -1;
    else for (int i = 0;i < n;i++) cout << ans[i];
    cout << '\n';
    return 0;
}

G.KFC Crazy Thursday

  • 题意

    • 回文串模版题
  • 题解

    • 板子如下,不会,捏吗
  • 代码

#include<bits/stdc++.h>
#define MAXN 500010
using namespace std;
int n, tot = 1, last, cur, pos;
int len[MAXN], num[MAXN], fail[MAXN], trie[MAXN][26], ans[4];
char s[MAXN];
int getfail(int now,int i){
	while(i - len[now] - 1 < 0 || s[i-len[now]-1] != s[i]) now = fail[now];
	return now;
}
int main(){
	while(~scanf("%d%s", &n, s)){
		memset(ans, 0, sizeof(ans));
		memset(num, 0, sizeof(num));
		memset(len, 0, sizeof(len));
		memset(trie, 0, sizeof(trie));
		memset(fail, 0, sizeof(fail));
		tot = 1; last = 0; cur = 0; pos = 0;
		fail[0] = 1; len[1] = -1;
		for(int i = 0; i <= n - 1; i++){
			pos = getfail(cur,i);
			if(!trie[pos][s[i]-'a']){
				fail[++tot] = trie[getfail(fail[pos],i)][s[i]-'a'];
				trie[pos][s[i]-'a'] = tot;
				len[tot] = len[pos] + 2;
				num[tot] = num[fail[tot]] + 1;
			}
			cur = trie[pos][s[i]-'a'];
			last = num[cur];
			if(s[i] == 'k') ans[1] += last;
			else if(s[i] == 'f') ans[2] += last;
			else if(s[i] == 'c') ans[3] += last;
		}
    printf("%d %d %d\n", ans[1], ans[2], ans[3]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值