第三次暑假集训

A - Prime Gift

  • 题意:给你n个质数,让你求这n个质数能乘起来组成的第K大数(n≤16)
  • 做法:这里使用折半查找和二分
  • 具体做法
    • ①:先把n个质数分为两个集合,最好相邻的分别放入不同集合
    • ②:分别求出这两个集数字可能组合相乘得到的值
    • ③:再用二分,遍历一下第一个集合中的数字,和第二个集合中可能的小于mid的组合个数,判断一下是否等于k,如果是mid即为解

代码

#include
#define il inline
#define rg register
#define ldb double
#define lst long long
#define rgt register int
#define N 20
#define pb push_back
using namespace std;
const lst Inf=1e18;
il int read()
{
    int s=0,m=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')m=1;ch=getchar();}
    while( isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
    return m?-s:s;
}
 
lst n,K;
vector<lst> v[3];
lst p[N],Sz[3];
 
void Dfs(int rt,int now,lst ss)
{
    if(now>n){v[rt].pb(ss),++Sz[rt];return;}
    for(lst w=1;;w*=p[now])
    {
        Dfs(rt,now+2,ss*w);
        if((1e18)/p[now]<w*ss)return;
        //用除法是因为怕爆long long
    }
}
 
int main()
{
    n=read(),v[1].pb(0),v[2].pb(0);
    for(rgt i=1;i<=n;++i)p[i]=read();
    sort(&p[1],&p[n+1]),Dfs(1,1,1),Dfs(2,2,1);
    sort(&v[1][1],&v[1][Sz[1]+1]);
    sort(&v[2][1],&v[2][Sz[2]+1]);
    lst le=1,ri=1e18,mid,tot,Ans;K=read();
    while(le<=ri)
    {
        mid=(le+ri)>>1,tot=0;
        for(rgt i=1,j=Sz[2];i<=Sz[1]&&j>=1;++i,tot+=j)
            while(j&&mid/v[1][i]<v[2][j])--j;//用除法是因为怕爆long long
        if(tot<K)le=mid+1;
        else Ans=mid,ri=mid-1;
    }return printf("%lld\n",Ans),0;
}

B - Maximum Value

  • 题意:给你n个数字,求这几个数字相互取余的最大值是多少
  • 做法:由于对于一个固定的数字取模的话,最大值+ak再取模的结果是一样的,那么这个最大值肯定是ak ~ a*(k+1)之间的最大的哪一个,所以只要枚举所有数字为模值的情况,然后对于每一种情况都找一下再ak ~ a(k+1)区间的最大值是哪一个,模的结果取最大值即可

代码

#include 
#include 
#include 
 
using namespace std;
const int maxn = 1e6+5;
 
int N, a[maxn];
 
int solve (int x) {
    int ret = 0, p = x;
    while (p < maxn) {
        p += x;
        int k = lower_bound(a, a + N, p) - a;
 
        if (k == 0)    continue;
        else k--;
 
        if (a[k] <= x) continue;
 
        ret = max(ret, a[k] % x);
    }
    return ret;
}
 
int main () {
    scanf("%d", &N);
    for (int i = 0; i < N; i++)
        scanf("%d", &a[i]);
    sort(a, a + N);
 
    int ans = 0;
    for (int i = N-1; i >= 0; i--) {
        if (ans >= a[i] - 1)
            break;
        if (i < N - 1 && a[i] == a[i+1])
            continue;
        ans = max(ans, solve(a[i]));
    }
    printf("%d\n", ans);
    return 0;
}

C - Cinema

  • 题意:n个科学家,m部电影,每一个电影都有一个ai,表示这个电影音频语言,bi表示电影的字幕语言,如果一个科学家能会一个电影的音频语言,他就会非常满意,如果会字幕语言,就会比较满意,如果都不会就不满意了,问最后要让非常满意的科学家尽可能多,如果相等,就然比较满意的尽可能多,问最后选什么电影
  • 做法:map存下每一个语言科学家有多少,然后根据不同的满意程度来遍历一下所有电影即可,细节代码中有

代码

#include  
#include  
#include  
#include  
using namespace std;
 
#define inf int(0x3f3f3f3f)  
#define mod int(1e9+7)  
typedef long long LL;
enum{ N = int(2e5 + 10) };
int a[N], b[N], c[N];
map<int, int>qq;
int main()
{
	int n, m;
	while (~scanf("%d", &n))
	{
		qq.clear();
		for (int i = 0; i < n; i++)
		{
			scanf("%d", a + i);
			qq[a[i]]++;
		}
		scanf("%d", &m);
		int sud = -1, lag = -1, ans = -1;
		for (int i = 0; i < m; i++)scanf("%d", b + i);
		for (int i = 0; i < m; i++)scanf("%d", c + i);
		for (int i = 0; i < m; i++)
		{
			if (sud < qq[b[i]] || sud <= qq[b[i]] && lag<qq[c[i]])
			{
				sud = qq[b[i]];
				lag = qq[c[i]];
				ans = i + 1;
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

D - DNA Evolution

  • 题意:给你一个只有“T",“A”,"G’,'C’组成的序列,有m个操作,操作1表示把第i个字符改成a,操作2表示给你一个序列e,e无线循环,原序列区间[l,r]再e无限循环序列中和e重合的字符有几个
  • 做法:这题目的难点在于设定树状数组的定义,由于对于同一个字符来说,一个再原序列的位置为x,一个为y,如果x%strlen(e)==y%(strlen(e)的话,也就是说再e中的位置取模相同,举个例子,TATA为原序列,TA为e序列,那么第一个T和第二个T的在e的无限循环序列TATATA…中取模位置相同都是0,那么这两个T的匹配结果肯定相同,在这个例子中都是1
  • 知道这个之后,在利用e的长度最多为10,我们就可以开出一个可接受大小的树状数组了,把所有的需要的条件包括进去即可,分别为:一个字符是什么,在原序列中的位置是多少,e的长度是多少,该字符在e中位置是多少。
  • 即node[4][11][11][maxn];
  • 接下来就是一个普通的树状数组了,直接看代码即可

代码

#include
#include
#include
#include
using namespace std;
 
const int maxn = 1e5+10;
int node[4][11][11][maxn];  
int h[110];
char s[maxn], e[11];
 
int lowbit(int x){
    return x&(-x);
}
 
void add(int ty, int pos, int x, int d){  //ty:字母的种类 pos:相对位置 x:实际位置 d:变换的值
    for(; x < maxn; x+=lowbit(x)){
        for(int k = 1; k <= 10; k++){
            node[ty][k][pos%k][x] += d;
        }
    }
}
 
int sum(int ty, int len,int pos, int x){  //ty:字母的种类 len:e子串的长度 pos:相对于e子串的位置 x:实际位置
    int ans = 0;
    for(; x > 0 ; x-=lowbit(x)){
        ans += node[ty][len][pos][x];
    }
    return ans;
}
 
int query(int l,int r,int ty,int len,int pos){  //区间[l,r]
    return sum(ty, len, pos, r) - sum(ty, len, pos, l-1);
}
 
int main(){
    int n;
    h['A'] = 0; h['T'] = 1; h['C'] = 2; h['G'] = 3;  //给四个字母标记数字
    scanf("%s%d",s,&n);
    int len = strlen(s);
    for(int i = 0;i < len; i++){
        add(h[s[i]], i, i+1, 1);  //因为树状数组是从1开始,则i+1为实际位置。但是相对位置之后要取模所以不能+1,就按照原来的位置取模完才能会是正确的相对位置
    }
    while(n--){
        int a, b, c;
        scanf("%d",&a);
        if(a == 1){
            scanf("%d%s",&b,e);
            add(h[s[b-1]], b-1, b, -1);  //把s[b-1]原来的字母的数量减去
            add(h[e[0]], b-1, b, 1);   //把新的字母的数量+1
            s[b-1] = e[0];
        }
        else{
            scanf("%d%d%s",&b,&c,e);
            len = strlen(e);
            int ans = 0;
            for(int i = 0; i < len; i++){  //枚举每个e字符串的上的字符的数量。
                ans += query(b, c, h[e[i]], len, (i + b -1)%len);  //这里几个(i+b-1)要记得取模len,不然就凉了
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}

E - Yaroslav and Divisors

G - I Hate It

  • 做法:用线段树模板,记一下每个区间的最大值即可

代码

#include 
#include 
#include 
#include 
using namespace std;
const int N=200010;
struct point{
    int l,r;
    int v;
}tr[N*4];
int f[N];
int n,m;
void push_up(int u){
    tr[u].v=max(tr[u<<1].v,tr[u<<1|1].v);
}
int query(int u,int l,int r){
    int v=0;
    if(tr[u].l>=l&&tr[u].r<=r)return tr[u].v;//如果当前的树已经被包含再查询区间内了,直接放回v
    int mid=tr[u].l+tr[u].r>>1;
    if(l<=mid)v=max(v,query(u<<1,l,r));//否则分别考虑再左节点和右节点是否有查询区间
    if(r>mid)v=max(v,query(u<<1|1,l,r));
    return v;
}
void build(int u,int l,int r){//建树
    tr[u]={l,r};
    if(l==r){
        tr[u].v=f[l];
        return;
    }
    int mid=l+r >> 1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
    push_up(u);
}
void modify(int u,int x,int v){
    if (tr[u].l == x && tr[u].r == x) tr[u].v = v;//如果已经找到了要添加的位置,就直接赋值
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        push_up(u);
    }
    
}
int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        for(int i=1;i<=n;i++){
            scanf("%d",&f[i]);
        }
        build(1,1,n);
        while(m--){
            char c;
            int a,b;
            scanf(" %c%d%d",&c,&a,&b);
            if(c=='Q'){
                printf("%d\n",query(1,a,b));
            }else{
                modify(1,a,b);
            }
        }
    }
}

H - Glad You Came

  • 题意:给你一个整数数组a[],每个元素初始化为0. 然后进行 m 个操作,更新a[]中的元素,最后输出 (每个元素a[i]*i) 的 异或和;
  • 做法:直接暴力用线段树维护一下一个区间的最小值,修改的时候看一下是否v大于最小值,如果小于就不修改,查询的时候直接暴力求出每一个点的值,在乘i异或

代码

#include
#include
#include
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const int MM=1000010;
const int MOD=(1<<30);
unsigned f[MM<<4];
ll a[MM];
unsigned X,Y,Z;
unsigned get()
{
    X=X^(X<<11);
    X=X^(X>>4);
    X=X^(X<<5);
    X=X^(X>>14);
    unsigned W=X^(Y^Z);
    X=Y;
    Y=Z;
    Z=W;
    return Z;
}
void Push(int rt)
{
    a[rt]=min(a[rt<<1],a[rt<<1|1]);
}
void update(int L,int R,ll w,int l,int r,int rt)
{
    if(a[rt]>=w)
        return ;
    if(l==r)
    {
        a[rt]=w;
        return ;
    }
    int mid=(l+r)>>1;
    if(L<=mid)
        update(L,R,w,l,mid,rt<<1);
    if(R>mid)
        update(L,R,w,mid+1,r,rt<<1|1);
    Push(rt);
}
ll query(int L,int R,int l,int r,int rt)
{
    if(L==l&&r==R)
    {
        return a[rt];
    }
    ll ans=0;
    int mid=(l+r)>>1;
    if(L<=mid)
        ans=query(L,R,l,mid,rt<<1);
    if(R>mid)
        ans=query(L,R,mid+1,r,rt<<1|1);
    return ans;
 
}
int main()
{
    int ca;
    scanf("%d",&ca);
    while(ca--)
    {
        int n,m;
        mem(a,0);
        scanf("%d%d%d%d%d",&n,&m,&X,&Y,&Z);
        for(int i=1; i<=3*m; i++)
        {
            f[i]=get();
        }
        for(int i=1; i<=m; i++)
        {
            int l=min((f[3*i-2]%n)+1,(f[3*i-1]%n)+1);
            int r=max((f[3*i-2]%n)+1,(f[3*i-1]%n)+1);
            int v=f[3*i]%MOD;
            update(l,r,v,1,n,1);
        }
        ll sum=0;
        for(int i=1; i<=n; i++)
        {
            ll w=query(i,i,1,n,1);
            sum^=(w*i);
        }
 
        printf("%lld\n",sum);
    }
    return 0;
}
 
 

I - Creative Snap

代码

  • 题意:有2的n次方个基地,每个基地有若干人,现在要毁灭所有基地,有a和b两个值。选择2个及以上基地时,可以将它们一切为二地分别毁灭,如果这里有人,要花费人b长度,如果没人,花费a。求毁灭所有基地的最小花费。
  • 做法:直接暴力递归,对于每一个区间只有两种情况,要么不分开直接毁灭,这样根据区间内有无复仇者有两种不同算法,要么分开,这里就直接递归下去即可
#include
#include
#include
using namespace std;
long long n, k, a, b;
long long hero[1000001];
long long solve(long long l, long long r) {
    long long num = upper_bound(hero + 1, hero + 1 + k, r) - lower_bound(hero + 1, hero + 1 + k, l);
    if (num == 0) return a;
    long long ans = (r - l + 1) * b * num;
    if (l >= r) return ans;
    ans = min(ans, solve(l, (l + r) / 2) + solve((l + r) / 2 + 1, r));
    return ans;
}
int main() {
    cin >> n >> k >> a >> b;
    for (long long i = 1; i <= k; i++) cin >> hero[i];
    sort(hero + 1, hero + 1 + k);
    cout << solve(1, (1 << n));
}

你可能感兴趣的:(第三次暑假集训)