The Preliminary Contest for ICPC Asia Shanghai 2019【B F J G】

B. Light bulbs

题意:

一排 N 个初始关着的灯泡,M个操作,每个操作使得区间【L,R】的状态反转,求最后开着灯泡的数量

分析:

读完题首先想到的是差分,但是T*N有点大,而M表较小,于是考虑离散化区间操作的L,R+1,然后差分就行了;注意差分后每个点代表了原序列的一个区间

代码:

#include <bits/stdc++.h>

#define l first
#define r second
#define pii pair<int,int>
using namespace std;
typedef long long LL;
const int maxn = 2e3+15;
int T,n,m,c[maxn],cnt,a[maxn];
pii p[maxn];
inline int getid(int x){
    return lower_bound(c,c+cnt,x)-c;
}
int main(){
    cin >> T;
    for(int Case = 1; Case<= T; ++Case){
        scanf("%d %d",&n,&m); cnt = 0;
        memset(a,0,sizeof(a));
        for(int i = 0;i < m; ++i){
            scanf("%d %d",&p[i].l,&p[i].r);
            c[cnt++] = p[i].l;
            c[cnt++] = p[i].r+1;               //差分r+1
            p[i].r++;
        }
        sort(c,c+cnt); cnt = unique(c,c+cnt)-c;
        for(int i = 0;i < m; ++i){
            p[i].l = getid(p[i].l);
            p[i].r = getid(p[i].r);
            a[p[i].l]++; a[p[i].r]--;
        }
        for(int i = 1;i < maxn; ++i) a[i] += a[i-1];
        int ans = 0;
        for(int i = 0;i < maxn; ++i){
            if(a[i]&1) ans += c[i+1]-c[i];
        }
        printf("Case #%d: %d\n",Case,ans);
    }
    return 0;
}

  F. Rhyme scheme

题意:

咕咕咕

分析:

首先每一位的字母都是唯一的,那么就可以枚举每一位选什么;当然不能直接暴力出所有情况,比如枚举到第二位选A后,只需要判断后面一共有多少种情况即可,设后面一共有 x 种,如果x >= k,那么这一位选A就是合法的,否则 k -= x,继续枚举这一位选B,所以要预处理出每一位后面一共有多少情况;每一位的选择又受前面最大值的影响,类似数位dp的写法预处理即可

注意 k 的值最大会爆unsigned long long,使用__int128就好了

代码:

#include <bits/stdc++.h>

using namespace std;
typedef __int128 LL;
const int maxn = 30;
LL dp[maxn][maxn],k;
int T,n,a[maxn];
inline LL read() {
    LL x = 0, f = 1;
    char c = getchar();
    for (; !isdigit(c);c = getchar()) if (c == '-') f = -1;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    return x * f;
}
LL dfs1(int pos,int limit){
    if(pos == n+1) return 1;
    if(~dp[pos][limit]) return dp[pos][limit];
    LL sum = 0;
    for(int i = 0;i <= limit; ++i){
        sum += dfs1(pos+1,max(limit,i+1));
    }
    dp[pos][limit] = sum;
    return sum;
}
void dfs2(int pos,int limit,LL k){
    if(pos == n+1){
        for(int i = 1;i <= n; ++i) putchar(a[i]+'A');
        puts("");
        return ;
    }
    for(int i = 0;i <= limit; ++i){
        int p = max(limit,i+1);
        if(dp[pos+1][p] >= k){
            a[pos] = i;                    //标记第pos位选什么
            dfs2(pos+1,p,k);
            break;
        }
        else k -= dp[pos+1][p];
    }
}
int main(){
    cin >> T;
    for(int Case = 1;Case <= T; ++Case){
        cin >> n; k = read(); 
        for(int i = 0;i < maxn; ++i){
            for(int j = 0;j < maxn; ++j)
                dp[i][j] = -1;
        }
        for(int i = 0;i < 30; ++i) dp[n+1][i] = 1;
        dfs1(1,0);
        printf("Case #%d: ",Case); dfs2(1,0,k);
    }
    return 0;
}

 J. Stone game

题意:

咕咕咕

分析:

 设第一个集合的和:sum1,第二个集合的和:sum2,那么需满足 sum1 >= sum2 ,并且集合1中的最小值 >= abs(sum1-sum2),自然想到枚举第一个集合的最小值,然后再求剩下的划分方案;先从小到大排序,定义 dp[x][y] 表示第 x 个数到最后一个数能组成和为 y 的方案数,倒着dp即可;假设最小值为 a[x],那么第一个集合的和只可能是第 x 个到最后一个数组成的,正符合倒着dp,然后枚举第一个集合的和判断是否满足条件即可统计答案

 代码:

#include <bits/stdc++.h>


using namespace std;
typedef long long LL;
const int mod = 1e9+7;
const int maxn = 305;
const int maxm = 150100;
int dp[maxn][maxm],a[maxn],n,T,sum;
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i = 1;i <= n; ++i) scanf("%d",a+i),sum += a[i];
        sort(a+1,a+n+1);
        for(int i = 0;i <= sum; ++i) dp[n+1][i] = 0;
        dp[n+1][0] = 1; int ans = 0;
        for(int i = n;i > 0; --i){
            for(int j = 0;j <= sum; ++j){       
                dp[i][j] = dp[i+1][j];               
                if(j >= a[i]){                   //枚举第一个集合选a[i]为最小值,并且和为j
                    dp[i][j] += dp[i+1][j-a[i]];
                    if(j>=sum-j&&j-(sum-j)<=a[i]) ans += dp[i+1][j-a[i]]; 
                    if(ans >= mod) ans -= mod;
                }
                if(dp[i][j] >= mod) dp[i][j] -= mod;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

 G. Substring

题意:

咕咕咕

赛后补题:

大致做法就是暴力+hash,暴力是指将长度一样的匹配串一起算;暴力一遍原串(最多sqrt(1e5)次),那么就能得到原串中所有这个长度的子串,当然不能暴力的与匹配串比较,而是将子串 hash 后存入 map,就能 O(1) 得到与每个匹配串配对个数;由于两端必须一样,那么可以考虑将其作为 map 的下标,中间的字符可以乱序,所以不能用进制 hash,给每个字母对应一个质数,加法hash 即可;由于空间的限制,每次只用存对答案有贡献的值就好了

#include <bits/stdc++.h>

#define sz(x) (int)(x).size()
using namespace std;
typedef long long LL;
const int maxn = 2e4+24;
LL a[26]={34183,13513,152993,13591,19687,350869,111187,766091,769297,633469,752273,298651,617191,880421,136067,1408397,726899,458921,2133701,2599847,2730947,4696343,10267237,18941059,34078909,69208409};
int T,m,ans[maxn];
string s,t;
struct pii{
    int len,id,L,R;LL val;
}p[maxn];
LL gethash(string s){
    LL res = 0;
    for(int i = 1;i < sz(s)-1; ++i) res+=a[s[i]-'a'];
    return res;
}
bool cmp(pii a,pii b){
    return a.len < b.len;
}
unordered_map<LL,int> mp[26][26];
void solve(int len){
    LL res = 0;
    for(int i = 1;i < len-1; ++i) res+=a[s[i]-'a'];
    if(mp[s[0]-'a'][s[len-1]-'a'].count(res))
    mp[s[0]-'a'][s[len-1]-'a'][res]++;
    for(int i = len;i < sz(s); ++i){
        res = res-a[s[i-len+1]-'a']+a[s[i-1]-'a'];
        if(mp[s[i-len+1]-'a'][s[i]-'a'].count(res))
        mp[s[i-len+1]-'a'][s[i]-'a'][res]++;
    }
}
int main(){
    scanf("%d",&T);
    while(T--){
        cin >> s >> m;
        for(int i = 0;i < 26; ++i)
        for(int j = 0;j < 26; ++j)
            mp[i][j].clear();
        for(int i = 0;i < m; ++i){
            cin >> t;
            p[i] = (pii){sz(t),i,t[0]-'a',t[sz(t)-1]-'a',gethash(t)};
            mp[p[i].L][p[i].R][p[i].val] = 0;
        }
        sort(p,p+m,cmp); int per = 0;
        for(int i = 0;i < m; ++i){
            if(p[i].len != per) solve(p[i].len);
            per = p[i].len;
            ans[p[i].id] = mp[p[i].L][p[i].R][p[i].val];
        }
        for(int i = 0;i < m; ++i) printf("%d\n",ans[i]);
    }
    return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值