今天又只过了一题,连崩三天了!!!说明我的知识还有很多漏洞,各方面能力都还有很大欠缺!这三天的考试都暴露了很大的问题。
1。对算法了解不深入,不全面。好多算法只是听说过,或者略懂一点,但没有真正深入掌握。比如今天的二分图博弈,又忘了结论是什么。
2。写代码错误太多。这些错误在代码写得很熟练的时候往往很少犯,所以一定要多练习,慢慢提升自己的代码能力。如今天F题,dfs前忘了标记vis,导致路径上有重点,一直没调出来。
并且,一定要训练自己用眼调试代码的能力,边读边想每句代码的正确性,减少对对拍的依赖,因为考场上没时间对拍!
3。思维不够深入。好多时候模型转化还不够,只能想出一个时间复杂度较为接近的算法,但离正解还有一步,如果去写通常过不了反而浪费时间。比如今天G题本来是很简单的结论题,(看错题)想复杂去分治NTT。还有昨天nlogn求&最大值,只想到sqrt(n)分块,怎么优化常数都过不了。这种情况应该更深入的去想正解,不能心存侥幸,去写时间复杂度明显错误的算法!
4。读题不够仔细。英文比赛打得很少,所以对ACM题面的分析能力不够强。经常看掉题目特殊限制,从而想不出题,想错题,浪费很多时间。还有ACM中细节比OI中明显多很多,比如多组数据清空数组,特殊情况判定(特别是ACM必须AC,而OI少特判可能掉10分而已)。所以必须更加细致,思维更加全面,特别是把做法想出来的题快速写对!
5。套路不够熟练。可能是因为脱离OI太久的缘故但这绝不是借口!!任何和你一起训练的人都和你站在同一起跑线上,ACM是新的起点!所以多做题,认真补题,特别是多总结,多思考,多复习,静心努力,提高自己的水平,让自己能熟悉的转化出各种模型!
题解:
A:签到题。换个顺序枚举即可,思路很常见。应该10分钟写完的!
B:数位DP,求回文数个数(后四位为日期)。所以枚举后4为,对前面的数位DP,记录是否顶上界,不顶上界直接乘10^len即可
其实很好写啊!
#include<bits/stdc++.h>
using namespace std;
#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--)
typedef long long ll;
ll power[20];
ll L,R,d1,d2;
ll ans;
int T,mx[20],a[20],rev[10020],tot;
inline void read(ll &x,ll &y){
int num[20],cnt = 0;
register char ch = getchar();
while ( ch > '9' || ch < '0' ) ch = getchar();
while ( ch <= '9' && ch >= '0' ) num[++cnt] = ch - '0' , ch = getchar();
rep(i,cnt - 3,cnt) y = y * 10 + num[i];
rep(i,1,cnt - 4) x = x * 10 + num[i];
}
void init(){
power[0] = 1;
rep(i,1,18) power[i] = power[i - 1] * 10ll;
rep(i,1,12) mx[i] = 31;
mx[2] = 28 , mx[4] = mx[6] = mx[9] = mx[11] = 30;
rep(i,1,2000){
int cnt = 0,x = i;
rep(j,1,4) a[++cnt] = x % 10 , x /= 10;
rep(j,1,4) rev[i] = rev[i] * 10 + a[j];
}
rep(i,1,12) rep(j,1,mx[i]) if ( j % 10 ) tot++;
}
ll DP(int i,int j,int t2){
if ( i < j ){
if ( t2 ) return 0;
return 1;
}
// if ( !t1 ) return power[(i - j + 2) / 2];
ll res = 0;
//顶上界继续枚举,注意超过下界主要看当前位
res += DP(i - 1,j + 1,(a[i] > a[j]) || (t2 && (a[i] == a[j])));
//不顶上界直接统计答案,所以不用记录是否顶上界
res += power[(i - j) / 2] * a[i];
return res;
}
inline ll Calc(ll x,ll y){
int cnt = 0,fir = 0,c = 0; ll res = 0;
memset(a,0,sizeof(a));
while ( x ) a[++cnt] = x % 10 , x /= 10;
repd(i,cnt,cnt - 3) fir = fir * 10 + a[i];
rep(i,1,12){
rep(j,1,mx[i]){
c = i * 100 + j;
if ( (j % 10) == 0 || rev[c] > fir ) continue;
if ( rev[c] == fir ) res += DP(cnt - 4,1,c > y);
else res += power[(cnt - 3) / 2];
}
// cout<<res<<endl;
}
rep(i,0,cnt - 5) res += power[(i + 1) / 2] * tot;
return res;
}
int main(){
freopen("input.txt","r",stdin);
init();
scanf("%d",&T);
while ( T-- ){
L = d1 = R = d2 = 0;
read(L,d1) , read(R,d2);
ans = Calc(R,d2) - Calc(L,d1 - 1);
printf("%lld\n",ans);
}
return 0;
}
C:博弈,每次选一个数,是last+a[x]为质数,将它删去。
因为last+a[x]为奇数,所以一定一奇一偶,则转化为二分图(奇偶分治)然后就是二分图博弈。
然而怎么判断一个点是否一定在所有最大匹配上。在残量网络上bfs,如果S能到x,则x不一定在,否则一定在。注意判2时要删除0号节点
#include<bits/stdc++.h>
using namespace std;
#define maxn 1020
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define inf 1e8
typedef long long ll;
struct node{
int next,to,f;
}e[maxn * maxn * 2];
int head[maxn],cnt = 1,cur[maxn],S,T,dis[maxn],q[maxn],hh,tt;
int a[maxn],n,t,id,cnte,cnto,odd[maxn],even[maxn],maxflow;
void clear(){
rep(i,1,T) head[i] = 0;
cnte = cnto = 0 , cnt = 1 , id = maxflow = 0;
}
inline ll power(ll x,ll y,ll p){
ll res = 1;
while ( y ) {
if ( y & 1 ) res = res * x % p;
x = x * x % p;
y >>= 1;
}
return res;
}
inline bool check(ll a,int x,int d,ll p){
a = power(a,x,p);
if ( a == 1 || a == p - 1 ) return 1;
rep(i,1,d){
a = a * a % p;
if ( a== p - 1) return 1;
}
return 0;
}
inline bool isprime(int p){
if ( p <= 2 ) return 0;
//if ( p == 2 ) return 1;
int x = p - 1,cnt = 0;
while ( !(x & 1) ) x >>= 1 , cnt++;
rep(i,1,10){
int cur = rand() % (p - 1) + 1;
if ( !check(cur,x,cnt,p) ){ return 0; }
}
return 1;
}
inline void adde(int x,int y,int c){
e[++cnt].to = y;
e[cnt].next = head[x];
e[cnt].f = c;
head[x] = cnt;
e[++cnt].to = x;
e[cnt].next = head[y];
e[cnt].f = 0;
head[y] = cnt;
}
bool bfs(){
rep(i,1,T) dis[i] = 0;
tt = hh = 0 , q[tt++] = S , dis[S] = 1;
while ( hh < tt ){
int x = q[hh++];
for (int i = head[x] ; i ; i = e[i].next){
if ( e[i].f && !dis[e[i].to] ){
dis[e[i].to] = dis[x] + 1;
q[tt++] = e[i].to;
}
}
}
return dis[T];
}
int dfs(int x,int delta){
if ( x == T || !delta ) return delta;
int res = 0;
for (int &i = cur[x] ; i ; i = e[i].next){
if ( e[i].f && dis[e[i].to] == dis[x] + 1 ){
int d = dfs(e[i].to,min(delta,e[i].f));
res += d , delta -= d;
e[i].f -= d, e[i ^ 1].f += d;
if ( !delta ) return res;
}
}
if ( delta ) dis[x]= -1;
return res;
}
//直接删掉x重新跑,看流量是否相等也可以判断。注意判2的时候0也要删除
bool check(int x){ //一个点是否在所有最大匹配上
if ( !x ) return 1;
for(int i = 2 ; i <= cnt ; i += 2){
if ( e[i].to == x || e[i ^ 1].to == x || e[i].to == cnte || e[i ^ 1].to == cnte ){
e[i].f = e[i ^ 1].f = 0;
}
else{
e[i].f = 1 , e[i ^ 1].f = 0;
}
}
int c = 0;
while ( bfs() ){
rep(i,1,T) cur[i] = head[i];
c += dfs(S,inf);
}
if ( c == maxflow ) return 0;
return 1;
}
bool bfs2(int x,int y){
tt = hh = 0;
rep(i,1,T) dis[i] = 0;
q[tt++] = S, dis[S] = 1;
while ( hh < tt ){
int x = q[hh++];
for (int i = head[x] ; i ; i = e[i].next){
if ( e[i].f && !dis[e[i].to] && e[i].to != y ){
dis[e[i].to] = dis[x] + 1;
q[tt++] = e[i].to;
}
}
}
return dis[x];
}
//直接在残量网络上从S bfs,如果能到x,说明x不一定在最大匹配上,否则一定在
bool check2(){
if ( !bfs2(cnte,0) ) return 0;
for(int i = 2 ; i <= cnt ; i += 2){
if ( e[i].to == cnte || e[i ^ 1].to == cnte ){
e[i].f = e[i ^ 1].f = 0;
}
else{
e[i].f = 1 , e[i ^ 1].f = 0;
}
}
while ( bfs() ){
rep(i,1,T) cur[i] = head[i];
dfs(S,inf);
}
if ( id && bfs2(id,cnte) ) return 0;
return 1;
}
int main(){
freopen("input.txt","r",stdin);
scanf("%d",&t);
while ( t-- ){
clear();
scanf("%d",&n);
int tag = 0;
rep(i,1,n){
scanf("%d",&a[i]);
if ( isprime(a[i]) ) tag = 1;
if ( a[i] & 1 ) odd[++cnto] = a[i];
else even[++cnte] = a[i];
if ( a[i] == 2 ) id = cnte;
}
if ( !tag && !id ){ printf("Totodile\n"); continue; }
even[++cnte] = 0 , S = n + 2, T = n + 3;
rep(i,1,cnto){
rep(j,1,cnte)
if ( isprime(odd[i] + even[j]) ){
adde(j,i + cnte,1);
}
}
rep(i,1,cnto) adde(i + cnte,T,1);
rep(i,1,cnte) adde(S,i,1);
while ( bfs() ){
rep(i,1,T) cur[i] = head[i];
maxflow += dfs(S,inf);
}
//if ( !check(cnte) && check(id) )
if ( check2() ) printf("Totodile\n");
else printf("Bulbasaur\n");
}
return 0;
}
`
D:给n个数填入m个位置,是sigma(|a[i] - b[j]|)最小。
用线段树维护DP。DP时把匹配看成向左或向右的一段。
好题!但是写和调花了太长时间!应该快速理清楚思路然后写!注意细节,其实很好写,但要推清楚,查错很难!注意有相同数时只能把a[i]和b[i]的关系定为一种。
#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 1e18
typedef long long ll;
int a[maxn],b[maxn],c[maxn * 2],n,m,tot;
int T,root[maxn],cnt,ls[maxn << 5],rs[maxn << 5];
ll fl[maxn],mn[maxn << 5],lw[maxn],rw[maxn],suma[maxn],sumb[maxn];
int stack_[maxn],tops,lb[maxn],la[maxn],rb[maxn],ra[maxn],pa[maxn];
struct node{
int id,num,t;
/* bool operator < (node a)const{
if ( id == a.id ) return t > a.t;
return id < a.id;
}*/
}dt[maxn * 2];
inline bool cmp1 (node a,node b){
if ( a.id == b.id ){
if ( a.t == b.t ) return a.num < b.num;
return a.t > b.t;
}
return a.id < b.id;
}
inline bool cmp2(node a,node b){
if ( a.id == b.id ){
if ( a.t == b.t ) return a.num < b.num;
return a.t > b.t;
}
return a.id < b.id;
}
void clear(){
mn[0] = inf;
memset(fl,0x3f,sizeof(fl)) , fl[0] = 0;
rep(i,1,n) root[i] = 0;
rep(i,1,tot) ls[i] = rs[i] = 0;
tot = 0;
rep(i,1,m) lb[i] = la[i] = rb[i] = ra[i] = 0 , pa[i] = n + 1;
}
void pre(){
sort(c + 1,c + tot + 1);
rep(i,1,n) a[i] = lower_bound(c + 1,c + tot + 1,a[i]) - c;
rep(i,1,m) b[i] = lower_bound(c + 1,c + tot + 1,b[i]) - c;
sort(a + 1,a + n + 1) , sort(b + 1,b + m + 1);
int j = 1;
rep(i,1,m){
while ( j <= n && a[j] < b[i] ) j++;
if ( j > n ) break;
pa[i] = j;
}
rep(i,1,m) sumb[i] = sumb[i - 1] + c[b[i]];
rep(i,1,n) suma[i] = suma[i - 1] + c[a[i]];
}
void init(){
int mx = 0,mn = tot + 2;
//b向右匹配
tot = tops = 0;
rep(i,1,n) dt[++tot] = (node){a[i],i,-1};
rep(i,1,m) dt[++tot] = (node){b[i],i,1};
sort(dt + 1,dt + tot + 1,cmp1);
rep(i,1,tot){
int t = dt[i].t,cur = 0;
if ( t == 1 ) stack_[++tops] = dt[i].num , mx = dt[i].num;
else{
if ( !tops ) continue;
cur = stack_[tops--];
ra[cur] = dt[i].num , rb[cur] = mx , rw[cur] = suma[dt[i].num] - suma[pa[cur] - 1] - (sumb[mx] - sumb[cur - 1]);
}
}
//b向左匹配
tot = tops = 0;
rep(i,1,n) dt[++tot] = (node){a[i],i,-1};
rep(i,1,m) dt[++tot] = (node){b[i],i,1};
sort(dt + 1,dt + tot + 1,cmp2);
repd(i,tot,1){
int t = dt[i].t,cur = 0;
if ( t == 1 ) stack_[++tops] = dt[i].num , mn = dt[i].num;
else{
if ( !tops ) continue;
cur = stack_[tops--];
la[cur] = dt[i].num , lb[cur] = mn;
lw[cur] = -(suma[pa[cur] - 1] - suma[dt[i].num - 1]) + sumb[cur] - sumb[mn - 1];
}
}
//rep(i,1,n) cout<<a[i]<<" ";
//rep(i,1,m) cout<<b[i]<<" ";
}
ll query(int x,int l,int r,int L,int R){
if ( !x ) return inf;
if ( L <= l && R >= r ) return mn[x];
ll res = inf; int mid = (l + r) >> 1;
if ( L <= mid ) res = query(ls[x],l,mid,L,R);
if ( R > mid ) res = min(res,query(rs[x],mid + 1,r,L,R));
return res;
}
inline void update(int x){
mn[x] = min(mn[ls[x]],mn[rs[x]]);
}
void modify(int &x,int l,int r,int id,ll d){
if ( !x ) x = ++tot;
if ( l == r ){ mn[x] = d; return; }
int mid = (l + r) >> 1;
if ( id <= mid ) modify(ls[x],l,mid,id,d);
else modify(rs[x],mid + 1,r,id,d);
update(x);
}
void solve(){
tot = 0;
rep(i,1,m){
if ( lb[i] ) fl[i] = min(fl[lb[i] - 1],query(root[lb[i] - 1],1,n,1,la[i] - 1)) + lw[i];
if ( rb[i] ) {
ll cur = min(fl[i - 1],query(root[i - 1],1,n,1,pa[i] - 1)) + rw[i];
modify(root[rb[i]],1,n,ra[i],cur);
}
}
ll ans = min(fl[m],mn[root[m]]);
printf("%lld\n",ans);
}
int main(){
freopen("input.txt","r",stdin);
scanf("%d",&T);
while ( T-- ){
scanf("%d %d",&n,&m);
clear();
rep(i,1,n) scanf("%d",&a[i]) , c[++tot] = a[i];
rep(i,1,m) scanf("%d",&b[i]) , c[++tot] = b[i];
pre();
init();
solve();
}
return 0;
}
E:数学题。
#include<bits/stdc++.h>
using namespace std;
#define maxn 100020
#define rep(i,l,r) for (register int i = l ; i <= r ; i++)
typedef long long ll;
const ll p = 1e9 + 7;
int prime[maxn],cnt,tag[maxn],mul[1020];
int A,B,D,T,a[maxn],tot;
ll ans = 0,inv6;
ll power(ll x,ll y){
ll res = 1;
while ( y ){
if ( y & 1 ) res = res * x % p;
x = x * x % p;
y >>= 1;
}
return res;
}
void init(){
rep(i,2,100000){
if ( !tag[i] ) prime[++cnt] = i;
for(register int j = 1 ; j <= cnt && (i * prime[j]) <= 100000 ; j++){
tag[i * prime[j]] = 1;
if ( (i % prime[j]) == 0 ) break;
}
}
inv6 = power(6,p - 2);
}
inline ll Calc(ll n){
// if ( n & 1 ) n++;
return n * (n + 1) % p * (2 * n + 1) % p * inv6 % p;
}
void solve(){
ans = 0;
rep(i,0,(1 << tot) - 1){
int cur = 1,cnt = 0;
rep(j,0,tot - 1){
if ( i & (1 << j) ) cur *= a[j] , cnt++;
}
if ( cnt & 1 ) ans = (ans - Calc((D + 1)/cur) * cur % p * cur % p + p) % p;
else ans = (ans + Calc((D + 1)/cur) * cur % p * cur) % p;
}
printf("%lld\n",ans * ((ll)A * A % p + (ll)B * B % p) % p);
}
int main(){
freopen("input.txt","r",stdin);
init();
scanf("%d",&T);
while ( T-- ){
scanf("%d %d %d",&A,&B,&D);
if ( D & 1 ){ printf("0\n"); continue; }
else{
tot = 0; int x = D + 2;
//质数要筛到大于sqrt(n)
for (register int i = 1 ; prime[i] * prime[i] <= x ; i++){
if ( (x % prime[i]) == 0 ){
a[tot++] = prime[i];
while ( (x % prime[i]) == 0 ) x /= prime[i];
}
}
if ( x > 1 ) a[tot++] = x;
solve();
}
}
return 0;
}
F:找出一个图的K染色方案或输出长度为K的路径。
直接构建dfs树,每层一种颜色(因为只有返祖边,同层无边)
如果无法染色则深度>K
学习了一波图的K染色方案数(注意不是最大团,不能直接从不能染色的点dfs,明天问一下?)
这里写链接内容
G:序列只有1和2,问能构成的区间和抑或值。
结论:如果一个序列的一端为1,则1-sum都可以被表示
挺显然的,但当时没有往这方面想,光想着这么统计和的方案数。开始看成会被抑或掉那种,要求出现次数,所以就想到分治FFT(其实根本不用分治,前缀和卷一下就好,所以FFT技巧也不够熟练)
然后就找最左或最右的1,v=max(sum[r],sum[n - l + 1])即找出最长可被连续表示的数。然后数为v +2,v +4,v + 2k
一定要早点睡,我觉得这几天状态不好的一大原因就是没有睡好!任何事都不能与睡觉冲突!安排好时间!