背景:
這次的模擬賽啊,,是真的自閉,自閉到家了,,(ㄒoㄒ)這次是Cydiater出的題,真的是很有水平,跟歷年noip的題目難度差不多,但是很不好想的那種,然後就考了個Day1,Day2,結果,,,如果交了的話就是兩百分左右,
兩天的題,還是沒有過一等線,,,
(由於打的太菜了,就兩天都沒交,但是回過頭來想了想,以後還是好好交吧,不管考了多少,這樣的話,至少在之後老師記錄的折線圖中至少就可以看到自己每次考試的進步和退步了,要是總不交或者是隻有考好了才交的話最後看圖的時候就很尷尬了,就幾次好成績而已,剩下的還可能被認爲是沒有膽量交,這,確實不是很好,而且總有這樣的自卑心理,也不會有什麼好結果,在博客裏進行自我檢討一翻)
(而且今天老師找我單獨談話了,,,,就是說我沒有交的事情,儘管第一題A掉了,我也沒有膽量交,這樣還是沒有什麼結果啊,其實模擬賽重要的是這個氛圍和形式,都是在模擬真正考試的時候,這樣的話就會在真正考試的時候不着急不會發生意外了,但是我總不交就第一不會有訓練到自己的暴力能力,第二就沒有警示自己的小錯誤,這點還是缺少自信啊,我還是要好好連暴力,並建立起自信!!考前的目標!!)
題目:
Day1 題目:
T1:Dove 的疑惑(math)
簡化題目:
給出個同餘方程,其形式爲: 給出其中的,重要條件:,詢問對於一組確定的,有多少種的取值方式,是無法找到對應的滿足所有的同餘方程。
題解:
(這個題,由於剛開始就看到了題目中給定的範圍裏面有的範圍,就直接以爲這是個大凱的疑惑,直接對於樣例進行找規律,然後就認認真真的隨便找了一下,就找到了正解,,肉眼正解,這,,是真的出乎意料,這題A的,讓我好意外,,,)
還是要好好寫一下正解的:
(這份正解中標算的原話比較多,因爲有一些還是自己證不出來的,望大佬們在下面的評論中給出證法,誠摯感謝)
下面我們設 {},很顯然, 就可以轉換爲: ,這兩個是等效的。然而對於任意兩個不同的來說,如果,可以推出至少在 一個的時候有不同的{},(這裏可以手推一下,找幾個例子,按我的理解,其實就是在卡邊界)對於{}來說,如果不相同的話,那麼就恰好存在個存在{}而且互不相同。這樣就是從上面向下推了一遍。
想到這裏時,在回想一下,總情況數,就是,這樣的話,就容斥一下就好了,減去存在的情況數M。
(這裏也有的大神是從excrt的式子推出來的正解,我還是太菜了,不是很會,,,)
代碼:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL read()
{
LL s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
int n; LL ans=1,s=1;
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int main()
{
n=read();
for(int i=1,x;i<=n;i++) x=read(),ans*=x,s=s/gcd(s,x)*x;
printf("%lld",ans-s);
return 0;
}
T2:捉迷藏(hide)
簡化題目:
給定n 個節點的樹,有點權,對於每個節點詢問最大聯通塊的大小,使得該聯通塊包含該節點,同時含有偶數個點權爲0點。
題解:
(這個題給的24分的偶數,直接送的分數,之後的就想着打個的暴力吧,還打掛了,,,然後,,就死掉了)
正解:
由於要記錄整個樹的所有點答案就不能從一個點的最近0處考慮,那麼現在們來考慮一下,在一個權值爲0的點處,答案就可能在它的子樹內部和子樹的外部進行取max,也可能是他的子樹內外都有,是對於總答案的貢獻,這樣的話,我們就開兩個數組:,。這是用來記錄這個點子樹之外的貢獻和這個點子樹的貢獻。在dfs求每個子樹大小的時候就可以更新好這兩個數組的初值。
接下來就是標記的上下傳遞了,這裏要從上到下,從下到上進行統計,在向上統計的時候還比較麻煩,要對於對應點的一個區間進行邊界更新處理,這樣保證內外子樹的答案的合法性。
(具體操作看註釋吧,還是比較好懂的)
代碼:
#include<bits/stdc++.h>
using namespace std;
inline int read(){int r;int s=0,c;for(;!isdigit(c=getchar());s=c);for(r=c^48;isdigit(c=getchar());(r*=10)+=c^48);return s^45?r:-r;}
const int sea=1e6+7;
int n,tot,a[sea],up[sea],dw[sea],ans[sea],size[sea];
vector<int>v[sea];
template<typename T> inline bool cmax(T &a, T b) {return a < b ? a = b, 1 : 0;}
void dfs(int x,int fa)
{
size[x]=1;
for(int i=0;i<v[x].size();i++)
{
int y=v[x][i];if(y==fa) continue;
dfs(y,x); size[x]+=size[y];
}
if(a[x]==0)
{
for(int i=0;i<v[x].size();i++)
{
int y=v[x][i];if(y==fa) continue;
cmax(dw[y],size[y]);
}
cmax(up[x],n-size[x]);
}//初值統計
}
void dfs_up(int x,int fa)
{
for(int i=0;i<v[x].size();i++)
{
int y=v[x][i];if(y==fa) continue;
dfs_up(y,x); cmax(up[x],up[y]),cmax(ans[x],up[y]);
}//用up[y]來更新up[x]和ans[x],這整個操作是自底向上的,所以是向上傳遞標記
int p=0,q=0;
//這裏也可以理解成一個區間,p是在正向更新向上更新時的最下面的值,q是在逆向更新向上更新時的最下面的值
for(int i=0;i<v[x].size();i++)
{
int y=v[x][i];if(y==fa) continue;
cmax(dw[y],p),cmax(p,up[y]);//用p更新y的子樹內的貢獻,然後用子樹外的貢獻去更新p,達到一種邊界處理的效果
}
reverse(v[x].begin(),v[x].end());//這裏就是逆序了,倒序的操作,自行百度吧
//提醒一下這裏還是用vector吧,結構體真的不好寫,,
for(int i=0;i<v[x].size();i++)
{
int y=v[x][i];if(y==fa) continue;
cmax(dw[y],q),cmax(q,up[y]);
}
}
void dfs_dw(int x,int fa)
{
cmax(ans[x],dw[x]);
for(int i=0;i<v[x].size();i++)
{
int y=v[x][i];if(y==fa) continue;
cmax(dw[y],dw[x]);dfs_dw(y,x);
}
}//用dw[x]來更新ans[x]和dw[y],這整個操作是自上向底的,所以是向下傳遞標記
int main()
{
n=read(); for(int i=1;i<=n;i++) a[i]=read();
for(int i=2;i<=n;i++)
{
int x=read(),y=read();
v[x].push_back(y),v[y].push_back(x);
}
for(int i=1;i<=n;i++) tot+=(a[i]==0);
if(tot%2==0){for(int i=1;i<=n;i++) printf("%d\n",n);return 0;}
dfs(1,0); dfs_up(1,0); dfs_dw(1,0);
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}
T3: Cicada的序列(sequence)
簡化題目:
求區間連續取模的總和。
題解:
(不說了,就暴力就打掛了,,,,(ㄒoㄒ))
正解:
(表示題解上的分治啊,set啊bit啊都沒有看懂,但是有一個大佬的AC代碼,我聽了他的思路也覺得很ok,就照着他的想法訂正了,std真的,,好難懂)
這個線段樹寫的,是真的妙啊,真的佩服,這線段樹學的,又一次覺得自己的線段樹白學了,,,o(╥﹏╥)o,這個就是用線段樹對於的操作進行優化,優化成的。
(具體操作看註釋吧,還是比較好懂的)
代碼:
#include<bits/stdc++.h>
#define lk k<<1
#define rk k<<1|1
#define LL long long
using namespace std;
int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int ocean=1000000001;
const int sea=3e5+7;
struct hit{int l,r,w;}tr[sea*4];
int n; LL ans=0,sum=0,a[sea];
void build(int k,int l,int r)
{
tr[k].l=l,tr[k].r=r;
if(l==r){tr[k].w=a[l];return ;}
int mid=(l+r)/2;
build(lk,l,mid);build(rk,mid+1,r);
tr[k].w=min(tr[lk].w,tr[rk].w);//這裏是都要取最小,是因爲要找這個點右邊第一個大於他的數,這樣就可以對這一段區間的值進行壓縮處理
}
void alter(int k,int x)
{
int l=tr[k].l,r=tr[k].r;
if(l==r){tr[k].w=ocean;return ;}//全賦成正無窮
int mid=(l+r)/2;
if(x<=mid) alter(lk,x);
else alter(rk,x);
tr[k].w=min(tr[lk].w,tr[rk].w);
}
int ask(int k,int x)
{
int l=tr[k].l,r=tr[k].r;
if(l==r) return l;
if(tr[k].w>x) return n+1;;//要是再左邊就賦成最大值,這樣就跑不到左邊了
if(tr[lk].w<=x) return ask(lk,x);//首先更新右邊的
return ask(rk,x);
}
int main()
{
n=read(); for(int i=1;i<=n;i++) a[i]=read();
build(1,1,n);
for(int i=1;i<=n;i++)
{
alter(1,i); //先處理一下
int j=i+1;LL last=a[i];ans+=a[i];
while(j<=n)
{
int w=ask(1,last); ans+=(w-j)*last;//這一段區間裏面就可以直接乘了,這也就是log算法的最大作用
if(w<=n) last=last%a[w]; j=w;//左指針移動
}
}
printf("%lld\n",ans);
//補的掛掉的暴力/(ㄒoㄒ)/~~
// if(n<=5000)
// {
// for(int i=1;i<=n;++i)
// {
// int last=a[i]; ans+=a[i];
// for(int j=i+1;j<=n;++j) last=last%a[j],ans+=last;
// }
// printf("%d\n",ans);
// }
return 0;
}
Day1 總結:
這回的失利就失利在了暴力的能力,之前是隻顧着學習更多的算法了,其實都不是很紮實,暴力能力就不是很好,需要更多的洛谷橙到藍題的刷一刷,這樣提高一下自己的暴力能力。暴力能力確實很重要,要是沒有暴力能力,每道題就很難準確拿分了。
Day2 題目:
T1:Dove 愛旅遊(trip)
簡化題目:
給你一張無向圖,找到使最大的聯通子圖。求最大的。
題解:
(這個題啊,巨哭~~~~沒有看懂題,剛開始一直以爲是直接求最大聯通子圖的,那不就是整棵樹嘛,然後就想是不是有向圖,但又發現樣例推不出來,然後我就想了一個多小時,就放棄了,,然後就掛掉了)
正解:
樹形DP的裸題啊,,,由於要記錄絕對值,就可以把初始值賦成1和-1,這樣記錄子樹的大小就是對於答案的貢獻了,然後就兩邊dfs,一遍看湖泊–山川的答案,另一遍是山川–湖泊的答案,然後就取最大值就好。
代碼:
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int sea=1e6+7;
struct see{int ver,next;}e[sea<<1];
int n,ans,tot,f[sea],w[sea],a[sea],last[sea];
void add(int x,int y){e[++tot].next=last[x];e[tot].ver=y;last[x]=tot;}
void dfs(int x,int fa)
{
f[x]=w[x];
for(int i=last[x];i;i=e[i].next)
{
int y=e[i].ver;if(y==fa) continue;
dfs(y,x); f[x]+=max(f[y],0);
}
ans=max(ans,f[x]);
}
int main()
{
freopen("trip.in","r",stdin);
freopen("trip.out","w",stdout);
n=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
if(a[i]==0) w[i]=-1;else w[i]=1;
}
for(int i=1,x,y;i<n;i++)x=read(),y=read(),add(x,y),add(y,x);
dfs(1,0);
for(int i=1;i<=n;i++) w[i]*=-1;
dfs(1,0); printf("%d\n",ans);
return 0;
}
T2:Cicada 愛燒烤(string)
簡化題目:
給你一個長度,一個字符集,讓你隨意放字符,保證放出來的串是迴文的而且任意位的前綴不爲迴文,求方案數。
題解:
(由於第一題沒有看懂題,我就一直在肝第二題,肝了兩個小時啊,,,我考場上就想到了O(n)的遞推,推出來了前五種情況,但是,,,,後面好像推的不是很對,然後就死掉了,我就找了找規律第二天又從早上六點訂正到了九點才訂正完,真的很難想,,,)
正解:
我推了通項公式(一個困擾了我很久的錯解),但是相對於模數,我怕由於取模得原因WA掉,就又推出來了遞推式,然後還是死在了取模上,我就直接看了看一位大佬的AC代碼,結果發現其實就是那個遞推式,只是取模沒取好,,,,
首先我們應該知道:若一個非迴文子串是一個待定串的前一半的話,這個待定串就一定合法
所以就只從考慮就行了,由於正着推不是太好推,就需要逆着容斥一下,先考慮總情況數,前半段的總情況數就是 ,然後就是每次在加上一個數的時候所加上的不合法的情況:以三個字符爲例:當你確定了第一個字符的時候,(這裏只求前半段,就是 )有,這就是個的,那麼m個字符就是,個的,由於如果這半段不合法的話,前提就是他的前半段合法,所以每次加上的一個位置就會乘上個,再加上,然後拉回視線,回到容斥的部分,就是用總情況中減去不合法的就好。這樣就可以推出遞推式,由於是要迭代,我在代碼中處理的比較清晰。
這時候可以發現,,這樣其實就是在分成兩半的時候的中點是在位置上或者是在位置與位置的間隔處,但是由於奇數情況是要影響前面的,所以和後面的偶數進行合併,
(這點可能有不同的理解,奇偶數怎麼理解都行,,)
千萬不要試圖求通項!!後果自負!!
f[1]=m
f[2]=m
f[3]=m^2-m
f[4]=m^2-m
f[5]=m3-2*m2+m
f[6]=m3-2*m2+m
f[7]=m4-2*m3+m
f[8]=m4-2*m3+m
f[9]=m5-2*m4+m
f[10]=m5-2*m4+m
……
是不是有發現什麼?
還是寫一下我這個“假”通項吧:
f[1]=f[2]=m,f[3]=m*(m-1) (i<=2)
f[i]=m{\frac{i+1}{2}}-2*m{\frac{i+1}{2}-1}+m (2<=i<=n)
就是這個錯解,害我又花了兩個小時去想正確的通項,結果發現並沒有什麼很好的規律,而且可能還沒有代數解有的話,好像要n階差分,再求,要是沒有的話,就要上羣論試試,我真的服了,,,棄療棄療,,,
代碼:
#include<bits/stdc++.h>
#define mod 1000000007
#define LL long long
using namespace std;
inline LL read()
{
LL s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
LL ksm(LL a,LL b)
{
LL s=1;
while(b)
{
if(b&1) s=s*a%mod;
a=a*a%mod;
b>>=1;
}
return s%mod;
}
const int sea=1e6+7;
LL n,m,f[sea];
//f[i]表示第i個位置可以形成合法的串有多少個
//若一個非迴文子串是一個待定串的前一半的話,這個串就一定合法
int main()
{
// freopen("string.in","r",stdin);
// freopen("string.out","w",stdout);
n=read(); m=read();
f[1]=f[2]=m%mod;f[3]=f[4]=(m-1)*m%mod;
LL sum=(2*m-1)*m%mod;
for(int i=5;i<=n;i+=2)
{
f[i]=ksm(m,(i+1)/2);
f[i]=(f[i]-sum)%mod; f[i+1]=f[i];
sum=sum*m%mod,sum=(sum+f[i/2+2])%mod;
}
// for(int i=5;i<=20;i++) f[i]=(ksm(m,((i+1)/2))-2*ksm(m,((i+1)/2-1))+m)%mod,printf("%d ",f[i]);
if(f[n]<0) f[n]+=mod; printf("%lld\n",f[n]);
return 0;
}
T3:Dove 的博弈(game)
簡化題目:
給定 個數 和常數 ,如果或者 就意味着 可以把 或者喫掉,如果它全出吃了那就輸出他的下標。
題解:
(由於T2浪費的時間太多而且爲了看懂T1,我浪費了三個多小時,這個題就直接放棄了,,其實是能拿一些暴力分的,而且要是暴力打得好就正解了,,,(ㄒoㄒ))
正解:
這個題應該很好想到正解,畢竟暴搜的話就也能拿部分分,而且的辦法也比較好想了。
考慮一下在向外擴的時候你可以先找到現在位置上可以像左右擴到的最大情況,就是直接找出左邊和右邊第一個大於這個數的下標,然後再向外一步一步擴就好,這樣就是O(n)的了。(可以剛開始用前綴和進行一個小操作的優化)
(提醒:這個題輸出巨大,hsm的快寫都炸了,,但不知道爲什麼printf卻能完好無損的跑過,,,)
代碼:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int ocean=1e9+7;
const int sea=8e6+7;
deque<int>dd;
int n,m,cl[sea],cr[sea],f[sea];LL a[sea],sum[sea];
int dfs(int x)
{
if(f[x]!=-1) return f[x];
if(cl[x]==0&&cr[x]==n+1) return f[x]=1;
f[x]=0; LL s=sum[cr[x]-1]-sum[cl[x]]+m;
if(cl[x]>0&&s>=a[cl[x]]) f[x]=f[x]|dfs(cl[x]);
if(f[x]==0&&cr[x]<=n&&s>=a[cr[x]]) f[x]=f[x]|dfs(cr[x]);
return f[x];
}
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
n=read(); m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
a[0]=a[n+1]=ocean; dd.push_back(0);
for(int i=1;i<=n;i++)
{
while(!dd.empty()&&a[i]>=a[dd.back()]) dd.pop_back();
cl[i]=dd.back(); dd.push_back(i);
}
dd.clear(); dd.push_back(n+1);
for(int i=n;i>=1;i--)
{
while(!dd.empty()&&a[i]>a[dd.back()]) dd.pop_back();
cr[i]=dd.back(); dd.push_back(i);
}
memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++) if(dfs(i)) printf("%d ",i);
return 0;
}
Day2 總結:
整體來說,我的讀題能力啊,,,欲哭無淚,,,要是讀好了也不至於考場上心態爆炸了,,就是心態啊,心態被T1全搞掉了,,o(╥﹏╥)o,所以要好好讀題啊,,,,
總結:
這次的模擬賽,考了一個數論,一個圖論,一個數據結構,一個樹形DP,一個求遞推式,一個初級數據結構(搜索),還是比較全面的,雖然沒有達到線,但是我找到了自己的一些不足,一是暴力能力,二是讀題能力及心態,繼續加油,才第一次的模擬賽,還有二十幾次呢,,,,