7月集训总结 2018.7.26

今天是7月集训最后一天。7月总共12天的集训告一段落。
总共参加了11场比赛,有10场没有发挥好。有些场特别差,只过了一两题。总成绩很差,19名,只能进入浙大B队。
每场比赛都有很多sb错误,对考场上状态反思不够,没有及时改正。考场上精神不够集中,老是在一个地方卡死,不敢去换一道题做。导致几乎每一场都有遗憾。还有经常看错题。
总之我的状态和水平都还很差,非常需要进一步提高。继续努力!在接下来的每一天完成任务,争取在8月集训前找回自己的状态!

7。25题解:
A:求长度为n的只包含两次“01”的01串。
看清题,答案=C(n + 1,5),n很大,直接矩阵快速幂
B:求字典序第k小子序列。
看清题:相同的子序列只算一次。
然后就是很简单的主席树上二分,按位确定。直接枚举的复杂度也是对的:因为只会有log(k)个不同的数。相同的数选最前面的因为所有都可以构造出来。

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define maxn 100020
#define N 2000020
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define inf 1e8


typedef long long ll;
const ll INF = 1e12;
inline int read(){
    register int num = 0;
    register char ch = getchar();
    while ( ch > '9' || ch < '0' ) ch = getchar();
    while ( ch <= '9' && ch >= '0' ) num = num * 10 + ch - '0' , ch = getchar();
    return num;
}
int n,T,k,mx;
int rt[maxn],ls[N],rs[N],a[maxn],pos[N],tot;
int ans[maxn],id[maxn],cnt;
ll sum[N],f[maxn],g[maxn];

void clear(){
    rep(i,1,n) g[i] = f[i] = 0 , id[i] = rt[i] = 0;
    rep(i,1,tot) sum[i] = 0 , pos[i] = ls[i] = rs[i] = 0;
    mx = tot = cnt = 0;
}
void init(){
    ll cur = 0;
    repd(i,n,1){
        f[i] = min(INF,cur + 1);
        cur = min(INF,cur + f[i] - f[id[a[i]]]);
        id[a[i]] = i;
    }
//  rep(i,1,n) cout<<f[i]<<" ";
//  cout<<endl;
}
inline void copy(int x,int y){
    ls[x] = ls[y] , rs[x] = rs[y] , sum[x] = sum[y];
}
inline void update(int x){
    sum[x] = min(INF,sum[ls[x]] + sum[rs[x]]);
}   
void insert(int &x,int y,int l,int r,int id,ll d,int p){
    x = ++tot;
    if ( l == r ){ pos[x] = p; sum[x] = d; return; }
    int mid = (l + r) >> 1;
    copy(x,y);
    if ( id <= mid ) insert(ls[x],ls[y],l,mid,id,d,p);
    else insert(rs[x],rs[y],mid + 1,r,id,d,p);
    update(x);
}
int query(int x,int l,int r,int &k){
    if ( l == r ) return pos[x];
    int mid = (l + r) >> 1;
    if ( k <= sum[ls[x]] ) return query(ls[x],l,mid,k);
    k -= sum[ls[x]];
    return query(rs[x],mid + 1,r,k);
}
int main(){
    freopen("input.txt","r",stdin);
//  freopen("1.out","w",stdout);
    scanf("%d",&T);
    while ( T-- ){
        clear();
        scanf("%d %d",&n,&k);
        rep(i,1,n) scanf("%d",&a[i]),mx = max(mx,a[i]);
        init();
        repd(i,n,1){
            insert(rt[i],rt[i + 1],1,mx,a[i],f[i],i);
        }
        int cur = 0;
        while ( 1 ){
            cur = query(rt[cur + 1],1,mx,k);
            k-- , ans[++cnt] = a[cur];
            if ( k <= 0 ) break;
            if ( cur == n ) break;
        }
        printf("%d\n",cnt);
        rep(i,1,cnt) printf("%d ",ans[i]);
        printf("\n");
    }
    return 0;
}

C题:给一张有向图,求删除每一个点,是否可以让强连通分量增加。
支配树好题。对于每个SCC,选一个点,跑支配树(将边反向后再跑一次),结论是如果这个点在任一支配树中是其他点的支配集,就会使强连通分量增加。最后把选的那个点删除再跑一次SCC check
明天写,该复习支配树了!

D题:给n个点,选两点构成矩形,问最多包含多少个点(两点中一个点在另一个点的左上角)
维护左上和右下边界,从左往右扫描,用线段树维护左上点的最大值,扫到右下时查询。因为右下边界y坐标递增,用一个set维护所有点,然后弹出y坐标比当前查询点小的。
经典的扫描线!
注意set要用multiset,因为y有重。还有set的遍历,用iterator

#include<bits/stdc++.h>
using namespace std;
#define maxn 100020
#define N 400020
#define rep(i,l,r) for (register int i = l ; i <= r ; i++)
#define repd(i,r,l) for (register int i = r ; i >= l ; i--)

const int inf = 1e9;
struct node{
    int y,l,r;  
    node(){};
    node(int y,int l,int r):y(y),l(l),r(r){};
    bool operator < (node a)const{
        if ( y  == a.y ) return 0;
        return y < a.y;
    }
};
struct point{
    int x,y;
    point(){};
    point(int x,int y):x(x),y(y){};
}up[maxn],down[maxn];
multiset <node> s; //要用可重集。并且<只定义了y就是只与y有关的set
multiset <node>::iterator it1,it2,last;
int mx[N],ls[N],rs[N],tot,add[N],root;
int n,T,m,ans,cnt;
int y[maxn];

inline bool cmpy(point p,point y){ return p.y < y.y; }
inline bool cmpx(point p,point x){ return p.x < x.x; }
void clear(){
    rep(i,1,tot) add[i] = mx[i] = ls[i] = rs[i] = 0;
    tot = m = cnt = root = ans = 0;
    s.clear();
}
inline void ADD(int &x,int d){
    if ( !x ) x = ++tot;
    mx[x] += d , add[x] += d;
}
inline void pushdown(int x){
    if ( add[x] != 0 ){
        ADD(ls[x],add[x]);
        ADD(rs[x],add[x]);
        add[x] = 0;
    }
}
inline void update(int x){
    mx[x] = max(mx[ls[x]],mx[rs[x]]);
}
void modify(int &x,int l,int r,int L,int R,int d){
    if ( L > R ) return;
    if ( !x ) x = ++tot;
    if ( L <= l && R >= r ){ ADD(x,d); return; }
    pushdown(x);
    int mid = (l + r) >> 1;
    if ( L <= mid ) modify(ls[x],l,mid,L,R,d);
    if ( R > mid ) modify(rs[x],mid + 1,r,L,R,d);
    update(x);
}
int query(int x,int l,int r,int L,int R){
    if ( L > R ) return 0;
    if ( !x ) return 0;
    if ( L <= l && R >= r ) return mx[x];
    pushdown(x);
    int mid = (l + r) >> 1,res = 0;
    if ( L <= mid ) res = query(ls[x],l,mid,L,R);
    if ( R > mid ) res = max(res,query(rs[x],mid + 1,r,L,R));
    return res;
}
void build(int &x,int l,int r){
    x = ++tot;
    if ( l == r ) return;
    int mid = (l + r) >> 1;
    build(ls[x],l,mid) , build(rs[x],mid + 1,r);
}
void dfs(int x,int l,int r){
    cout<<x<<" "<<mx[x]<<endl;
    if ( l == r ) return;
    pushdown(x);
    int mid = (l + r) >> 1;
    dfs(ls[x],l,mid) , dfs(rs[x],mid + 1,r);
}
void solve(){
    int cur = 0;
    build(root,1,m);
    rep(i,1,n){
        int l = lower_bound(up + 1,up + m + 1,point(i,y[i]),cmpy) - up,r = upper_bound(up + 1,up + m + 1,point(i,y[i]),cmpx) - up - 1;
//      cout<<l<<" "<<r<<endl;
        node dt = (node){y[i],l,r};
        s.insert(dt);
        modify(root,1,m,l,r,1);
        if ( i == down[cur + 1].x ){
            ++cur;
            it2 = s.lower_bound(dt);
    //      dfs(root,1,m);
    //      cout<<endl;
            for (it1 = s.begin() ; it1 != it2 ; ++it1){ 
                modify(root,1,m,(*it1).l,(*it1).r,-1);
            //  cout<<(*it1).l<<" "<<(*it1).r<<" "<<(*it1).y<<endl;
            }
    //      dfs(root,1,m);
        //  cout<<endl;
            if ( it2 != s.begin() ) s.erase(s.begin(),it2); //set的删除左闭右开
            ans = max(ans,query(root,1,m,l,r));
            //last = it2;
        }
    }
    printf("%d\n",ans);
}
int main(){
    freopen("input.txt","r",stdin);
    scanf("%d",&T);
    while ( T-- ){
        clear();
        scanf("%d",&n);
        rep(i,1,n) scanf("%d",&y[i]);
        rep(i,1,n) if ( up[m].y < y[i] || !m ) up[++m] = point(i,y[i]);
        repd(i,n,1) if ( down[cnt].y > y[i] || !cnt ) down[++cnt] = point(i,y[i]);
        reverse(down + 1,down + cnt + 1);
        solve();
    }
    return 0;
}

E题:这里写图片描述
n,k<=1e5
sol1:直接枚举i和i*j,考虑把j划分为k个数相乘的方案。
对j的每个质因数分别组合数,插板法C(k+tot - 1,k - 1)。因为每个质因数之间无序,其他数有序。注意不是直接插板,不一定相邻质因数组合。
简单的组合数考场上没有退出来,去打表浪费了很多时间,应该静心推一下!
sol2:看成狄利克雷卷积,(f *g)(g(i) = 1)
ans = (f*g^k)因为卷积的结合律。
g数组快速幂时枚举倍数就好了
O(nlogn^2)
感觉在这道题上大材小用了,但是这种思路很常见。

//#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define maxn 100020
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define inf 1e8


typedef long long ll;
const ll mod = 1e9 + 7;
inline int read(){
    register int num = 0;
    register char ch = getchar();
    while ( ch > '9' || ch < '0' ) ch = getchar();
    while ( ch <= '9' && ch >= '0' ) num = num * 10 + ch - '0' , ch = getchar();
    return num;
}
ll fac[maxn * 2],inv[maxn * 2],d[maxn],g[maxn],h[maxn];
int a[maxn][20],tot[maxn][20];
int prime[maxn],cnt,tag[maxn],num[maxn],vis[maxn];
int n,k,T;

ll power(ll x,ll y){
    ll res = 1;
    while ( y ){
        if ( y & 1 ) res = res * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return res;
}
void init(){
    for (int i = 2 ; i <= 100000 ; i++){
        if ( !tag[i] ) prime[++cnt] = i;
        for (int j = 1 ; j <= cnt && prime[j] * i <= 100000 ; j++){
            tag[i * prime[j]] = 1;
            if ( (i % prime[j]) == 0 ) break;
        }
    }
    fac[0] = inv[0] = 1;
    for (int i = 1 ; i <= 150000 ; i++) fac[i] = fac[i - 1] * i % mod;
    inv[150000] = power(fac[150000],mod - 2);
    for (int i = 150000 - 1 ; i >= 1 ; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
}

void pre(){
    for(n = 2 ; n <= 100000 ; n++){
        int x = n ; cnt = 0;
        for (int i = 1 ; prime[i] * prime[i] <= x ; i++){
            if ( (x % prime[i]) == 0 ) a[n][++cnt] = prime[i];
            while ( (x % prime[i]) == 0 ) tot[n][cnt]++ , x /= prime[i];
        }
        if ( x > 1 ) a[n][++cnt] = x, tot[n][cnt] = 1;
        num[n] = cnt;
    }
//  num[1] = 1 , f[1][1] = 1;
}
inline ll C(int n,int m){
    if ( n == m || !m ) return 1;
    if ( n < m ) return 0;
    return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

int main(){
    freopen("input.txt","r",stdin);
//  freopen("1.out","w",stdout);
    init();
    pre();
    scanf("%d",&T);
    while ( T-- ){
        scanf("%d %d",&n,&k);
        rep(i,1,n) scanf("%lld",&d[i]);
        rep(i,1,n) g[i] = 0,vis[i] = 0;
        rep(i,1,n){
            ll cur = 0;
            for (register int j = 1 ; i * j <= n ; j++){
                cur = 0;
                if ( j == 1 ) cur = 1;
                else if ( vis[j] ) cur = h[j];
                else{
                    cur = 1;
                    for (register int y = 1 ; y <= num[j] ; y++){ //对每个质因数分别算组合数,因为相同质因数不存在顺序问题,只需考虑它插在哪个位置
                        //用插板法算组合数
                        cur = cur * C(k + tot[j][y] - 1,k - 1) % mod;
                    }
                    h[j] = cur; vis[j] = 1;
                }
                    g[i * j] = (g[i * j] + cur * d[i]) % mod;
            }
        }
    //  rep(i,1,n) cout<<h[i]<<" ";
    //  cout<<endl<<endl;
        rep(i,1,n){
            printf("%lld",g[i]);
            if ( i < n ) printf(" ");
            else printf("\n");
        }
    }
    return 0;
}

F题:求和第k大的子序列。看清题,k<=1e5
排序后直接用堆维护,每次取最小增广。
要么直接选后一个数,要么这个数和后一个一起选。
可以证明这样所有情况都可以枚举到。

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define maxn 100020
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define inf 1e8


typedef long long ll;
inline int read(){
    register int num = 0;
    register char ch = getchar();
    while ( ch > '9' || ch < '0' ) ch = getchar();
    while ( ch <= '9' && ch >= '0' ) num = num * 10 + ch - '0' , ch = getchar();
    return num;
}
int num[10];
inline void write(int x){
    register int cnt = 0;
    if ( !x ){ printf("0"); return; } //脪禄露拧脪陋脤脴脜脨0拢隆拢隆拢隆
    while ( x ) num[++cnt] = x % 10 , x /= 10;
    while ( cnt ) putchar(num[cnt--] + '0');
}
struct node{
    int d,id;
    bool operator < (node a)const{
        return d > a.d;
    }
};
priority_queue <node> heap;
int n,k,T,a[maxn];

int main(){
    scanf("%d",&T);
    while ( T-- ){
        while ( heap.size() ) heap.pop();
        scanf("%d %d",&n,&k);
        rep(i,1,n) scanf("%d",&a[i]);
        sort(a + 1,a + n + 1);
        heap.push((node){a[1],1});
        node ans;
        rep(i,1,k){
            ans = heap.top(); heap.pop();
            int d = ans.d , id = ans.id;
            if ( id < n ){
                heap.push((node){d + a[id + 1] - a[id],id + 1});
                heap.push((node){d + a[id + 1],id + 1});
            }
        }
        printf("%d\n",ans.d);
    }
    return 0;
}

G:扑克牌,直接讨论一下胜负情况,或者暴力用代码枚举也行。然后组合数。很简单,应该过掉的

H题:
这里写图片描述
找性质然后暴搜,好题!首先ai>=ci>=bi , ai,ci降序,bi升序,然后对ai和cj是否可以交换判定,利用最优性剪枝显著优化状态。
从1e9优化到几十。
注意不要把剪枝写错了,不然会T飞

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define maxn 120
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define inf 1e8


typedef long long ll;
inline int read(){
    register int num = 0;
    register char ch = getchar();
    while ( ch > '9' || ch < '0' ) ch = getchar();
    while ( ch <= '9' && ch >= '0' ) num = num * 10 + ch - '0' , ch = getchar();
    return num;
}
ll ans;
int a[maxn],b[maxn],c[maxn];
int dt[maxn * 3];
int n,T,cnt;

inline bool cmp(int x,int y){ return x > y; }
inline ll cal(){
    ll cur = 0;
    rep(i,1,n) cur += (a[i] - b[i]) * c[i];
    return cur;
}
void dfs(int x,int A,int C,ll res){
    if ( x == n * 3 + 1 ){
        ans = max(ans,res);
    //  if ( cnt % 100000 == 0 ) cout<<cnt<<endl;
        return;
    }
    if ( A < n ) a[A + 1] = dt[x],dfs(x + 1,A + 1,C,res);
    if ( C < A ){
        c[C + 1] = dt[x];
        rep(i,1,C){ //降序填,越来越小,注意剪枝时的顺序!
            if ( ((c[i] > a[C + 1]) && (a[i] < b[i] + c[C + 1])) || (c[i] < a[C + 1] && a[i] > b[i] + c[C + 1]) ) return;
        }
        dfs(x + 1,A,C + 1,res + (ll)(a[C + 1] - b[C + 1]) * c[C + 1]);
    }
}
int main(){
    freopen("input.txt","r",stdin);
    scanf("%d",&T);
    while ( T-- ){
        ans = 0;
        scanf("%d",&n);
        rep(i,1,n * 3) scanf("%d",&dt[i]);
        sort(dt + 1,dt + n * 3 + 1);
        rep(i,1,n) b[i] = dt[i];
    //  sort(dt + n + 1,dt + n * 3 + 1,cmp);
        reverse(dt + n + 1,dt + n * 3 + 1);
        dfs(n + 1,0,0,0);
        printf("%lld\n",ans);
    }
    return 0;
}

7。26题解

A;这里写图片描述
高中几何题,非常简单,讨论清楚即可。
第一列算式的时候要快,直接设成字母理清思路该怎么代值,把计算留给电脑
第二想清楚切线+一段圆弧最小

B:求n*n = (k +1) *k/2的第m个解,m<=1e12
贝尔方程,构造解
可以打表找规律,还可以差数列书
明天学习贝尔方程

扫描二维码关注公众号,回复: 2713006 查看本文章

C:简单树状数组优化dp。注意排序时or相同时,应该把ir小的放在后面,这样接下来的转移更优

D:二分图中霍尔定理的简单应用,不难。要是看了题应该很快能想出来,可惜没时间看题,所有时间分配不好!!
明天写!

E:这里写图片描述
集合n维前缀和裸题。静下心来看清题意就是一眼题。但是考场上看起来复杂就没有看,很不应该!
明天写!

F:区间&,| x,求区间最大值。
对每一位考虑,如果区间这一位都相同整体操作,转化为区间加。不同则暴力。复杂度有保证,因为区间操作后不同的数减少。复杂度O(nlog^2)但是不满,大胆尝试就可以过了!
明天写!

写在最后:
既然7月集训没有打好,那接下来就应该更加努力,完成好每一天的计划。

7、27
上午我要买自行车,所以休息半天
下午14:00-16:00 CFvirtual
16:00-18:00补题,并把div1剩的题做了
晚上18:30-21:30补今天的题
然后跑步,复习,总结。
11点一定要睡觉!

猜你喜欢

转载自blog.csdn.net/weixin_42484877/article/details/81229110