牛客寒假集训营第一场

A A
题意:
让你计算 n m n*m 矩形内,好的三角形的个数。
好的三角形定义如下:
1.三角形的三个顶点均为格点,即横坐标和纵坐标均为整数。
2.三角形的面积为 。
3.三角形至少有一条边和 轴或 轴平行。
honoka想知道,在平面中选取一个大小为 的矩形格点阵,可以找到多少个不同的“好三角形”?由于答案可能过大,请对1e9+7 取模。
思路:
计数。想了半天没有想到重复的情况,真是衰。直角三角形会被重复算一次,最后减掉即可。

inline ll mul(LL a,LL b,LL p)
{
    LL ans = 0;
    while(b)
    {
        if(b&1) ans = (ans + a)%p;
        b >>= 1;
        a = (a+a)%p;
    }
    return ans;
}
int main(){
    
    ll n,m;
    cin >> n >> m;
    ll s=(n-2)*(m-1)*4%mod+(n-1)*(m-2)*4%mod;
    ll d1 = mul(mul(m-2,m,mod),n-1,mod)*2%mod + mul(mul(n-2,n,mod),m-1,mod)*2%mod;
    ll d2 = mul(mul(m-1,m,mod),n-2,mod)*2%mod + mul(mul(n-1,n,mod),m-2,mod)*2%mod;
    ll ans = (d1%mod+d2%mod-s%mod)%mod;
    cout << ans;
}

B B
签到

C C
计算几何
在这里插入图片描述
思路:
我们将人与靶子连起来,与坐标轴会有交点,我们分别存与 x x 轴交点和与 y y 轴交点。显然与人在 y y 轴同一侧的点不能在 y y 周设置挡板来挡住它,与 x x 轴同一侧的点不能在 x x 周设置挡板来挡住他。
我们让尽量短的挡板来时可以射中的靶子不超过 k k 个,也就是说可以在挡板上至少挡住 n k n-k 个,
由于要求挡板尽量短,所以我们只需用挡板挡住 n k n-k 即可。
分别对 x x y y 轴双指针扫一遍即可。

vector<double> Qx,Qy;
int main(){
    double x0,y0;
 
    int n,k;
    cin >> x0 >> y0 >>  n >> k;
    for(int i = 0;i < n;++i){
        double x,y;
        cin >> x >> y;
        //if(id(pd1) == flag) return cout<<-1,0;
        if(y * y0  < 0){
            Qx.push_back(x0-y0*(x-x0)/(y-y0));
        }  
        if(x0 *x < 0) {
            Qy.push_back(y0-x0*(y-y0)/(x-x0));
        }
    }
    sort(Qx.begin(),Qx.end());
    sort(Qy.begin(),Qy.end());
    double M = 1e18;
    k = n - k;
    if(Qx.size() >= k){
        int S = Qx.size();
        for(int i = k-1,l = 0;i < S;++i,l++){
            M = min(M,Qx[i] - Qx[l]);
        }
    }
    if(Qy.size() >= k){
        int S = Qy.size();
        for(int i = k-1,l = 0;i < S;++i,++l){
            M = min(M,Qy[i] - Qy[l]);
        }
    }
    if(M == 1e18)  puts("-1");
    else printf("%.7lf",M);
}

D D
签到

E E
签到

F F
题意
有一天,maki拿到了一颗树。所谓树,即没有自环、重边和回路的无向连通图。
这个树有 n 个顶点,n-1 条边。每个顶点被染成了白色或者黑色。
maki想知道,取两个不同的点,它们的简单路径上有且仅有一个黑色点的取法有多少?
注:
①树上两点简单路径指连接两点的最短路。
② <p,q>和 <q,p> 的取法视为同一种。
思路:
这样的简单路径可以分为两类:
1、黑色节点为一条路径的端点
2、黑色路径不是一条路径的端点

我们首先缩点,将连在一起的白色节点用并查集处理缩点,并开一个数组 f f 保存每个白色节点集合的大小。
这样的话,假设对于某个黑点一共连接了 k k 个白色节点
第一种情况为 i = 1 k f ( i ) \sum\limits_{i=1}^{k}f(i)

第二种情况为 i = 1 k j = i + 1 k f ( i ) f ( k ) \sum\limits_{i=1}^{k}\sum\limits_{j=i+1}^{k}f(i)*f(k)

对于第二种情况我们预处理 f f 的前缀和,便可在 O ( k ) O(k) 的时间内处理一个黑色节点的情况。

ll pre[N];ll Size[N];
ll Find(ll x){return pre[x] == -1?x:pre[x] = Find(pre[x]);}
void join(ll x,ll y){
    ll q = Find(x);
    ll p = Find(y);
    if(q!=p){
        if(Size[q] > Size[p]) {pre[p] = q;Size[q] += Size[p];}
        else {pre[q] = p;Size[p] += Size[q];}
    }
}
char s[N];
vector<ll> Q[N];
ll num[N];
ll solve(ll x){
    ll S = Q[x].size();
    for(ll i = 0;i < S;++i){
        num[i+1] = num[i] + Size[Find(Q[x][i])];
    }
    ll sum = num[S];
    for(ll i = 0;i < S;++i){
        sum += Size[Find(Q[x][i])] * (num[S] - num[i+1]);
    }
    return sum;
}
int main(){
    memset(pre,-1,sizeof pre);
    ll t;
    cin >> t;
    fill(Size + 1,Size + t + 1,1);
    cin >> (s + 1);
    for(ll i = 1;i < t;++i){
        ll u,v;
        cin >> u >> v;
        if(s[u] == s[v] && s[u] == 'W') join(u,v);
        else if(s[u] == 'W' && s[v] == 'B'){Q[v].push_back(u);}
        else if(s[u] == 'B' && s[v] == 'W'){Q[u].push_back(v);}
    }
    ll sum = 0;
    for(ll i = 1;i <= t;++i){
        if(s[i] == 'B'){
            sum += solve(i);
        }
    }   
    cout << sum;   
}

G
在这里插入图片描述
思路:
双指针
对于每种字符我们用map计数,并且设置一个左端点 l l ,初始 l = 1 l=1 ,然后我们扫一遍字符串,随着扫的过程中,如满足了上述要求,我们尝试收缩左端点。即缩小区间,记录下来最小的区间即可。

using namespace std;
map<char ,int> hap;
int main(){
    int sum = 0;
    int n,k;
    cin >> n >>k;
    string s;
    cin >> s;
    int l = 0;
    int M = INF;
    for(int i = 0; i < n;++i){
        hap[s[i]] ++;
        while(l < n && hap[s[i]] >= k){
            if(hap[s[i]] > k) {hap[s[l]]--;l++;}
            else if(hap[s[i]]==k){
                if(s[l] != s[i]){hap[s[l]]--;l ++;}
                else break;
            }
            
        }
        if(hap[s[i]] >= k) M = min(M,i - l + 1);
    }
    if(M == INF) cout<<-1;
    else cout<<M;
}

还有一种方法是我们维护 a z a-z 每个字符在字符串中出现的位置,如果记录最小的长度即可。这种方法比较简单。而且代码好些。

int main(){
    int n,k;
    string s;
    cin >> n >> k;
    cin >> s;
    int ans = INF;
    for(int i = 0;i < 26;++i){
        vector<int> Q;int M = INF;
        for(int j = 0;j < n;++j){
            if(s[j] - 'a' == i) Q.push_back(j);
            int S = Q.size();
            if(S >= k){
                M = min(M,Q[S-1] - Q[S - k] + 1);
            }
        }
        ans = min(ans,M);
    }
    if(ans == INF) puts("-1");
    else cout << ans;    
}

H
在这里插入图片描述
思路:
1、二分
全1或者全0是两种情况,我们分开讨论,或者说对于全0这种情况我们把字符串中每个位置取反,0-1,1-0,这样就变为全为1这种情况。
下面讨论全为1这种情况
首先预处理前缀和,然后枚举字符串中每个位置 i i 为子串起点,然后二分寻找子符合要求的子串右端点 r r ,这个区间满足 n u m [ r ] n u m [ i ] + k > = r l + 1 num[r]-num[i]+k>=r-l+1
复杂度 O ( n l o g ( n ) ) O(nlog(n))

int sum[N],num[N];
int main(){
    int n,k;
    cin >> n >> k;
    string s;
    cin >> s;
    int l = s.size();
    for(int i = 0;i < l;++i){
        int d  = s[i] - '0';
        sum[i+1] = sum[i] + d;
        num[i+1] = num[i] + (!d);
    }

    int M = -INF;
    for(int i = 1;i <= n;++i){
        int l = i,r = n;
        while(l < r){//全1
            int mid = l + r + 1>> 1;
            int d1 = sum[mid] - sum[i-1];
            int d2 = mid - i + 1;
            if(d1 + k >= d2) l = mid;
            else r = mid - 1;
        }
        M = max(M,l - i + 1);
        l = i,r = n;
        while(l < r){//全0(这里转化为了全1)
            int mid = l + r + 1>> 1;
            int d1 = num[mid] - num[i-1];
            int d2 = mid - i + 1;
            if(d1 + k >= d2) l = mid;
            else r = mid - 1;
        }
        M = max(M,l - i + 1);

    }
    cout << M;

}

2、双指针(尺取)
也是先预处理前缀和,不过这个不用分开处理,直接处理好就行。(其实上面也是可以只处理一个的)
分别对两种情况尺取即可。和上面区别在于,二分方法枚举的是左端点,然后去找右端点;尺取是枚举右端点,并且维护一个左端点。

int sum[N];
int main(){
    int n,k;
    string s;
    cin >> n >> k >> s;
    for(int i = 0;i < n;++ i){
        int a = s[i] - '0';
        sum[i+1] = sum[i] + a;
    }
    int ans =0 ;
    int l = 0;
    for(int i = 0;i < n;++i){
        while(sum[i+1] - sum[l] + k < i + 1 - l) l ++;
        ans = max(ans,i+1-l);
    }
    l = 0;
    for(int i = 0;i < n;++i){
        while(sum[i+1] - sum[l] - k > 0) l ++;
        ans = max(ans,i+1-l);
    }
    cout << ans;
    
    
}

I
dp
在这里插入图片描述
思路:
d p [ i ] dp[i] 代表前 i i 个字符的最大分数
然后可得状态转移方程
在这里插入图片描述

ll dp[N];
int main(){
    ll n,a,b,c;
    cin >> n >> a >> b >> c;
    string s;
    cin >> s;
    ll M = -1;
    s.insert(0,1,'#');
    dp[0] = 0;
    for(ll i = 1;i <= n;++i){
        dp[i] = dp[i-1];
        if(i - 3 >= 1&&s.substr(i-3,4) == "nico") dp[i] = max(dp[i],dp[i-4]+a);
        if(i - 5 >= 1&&s.substr(i-5,6) == "niconi") dp[i] = max(dp[i],dp[i-6]+b);
        if(i - 9 >= 1&&s.substr(i-9,10) == "niconiconi") dp[i] = max(dp[i],dp[i-10]+c);
        M = max(M,dp[i]);
    }
    cout<< M;

}

J
矩阵快速幂待补

发布了589 篇原创文章 · 获赞 31 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_43408238/article/details/104175373
今日推荐