今天是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
贝尔方程,构造解
可以打表找规律,还可以差数列书
明天学习贝尔方程
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点一定要睡觉!