背景:
模擬賽定下來了,,週一週二週五,週三討論,這晚自習佔的,一看課表發現週三週四是語文和英語晚自習,這還是不停課嗎???白天的課,能(想)上的就上(主要上理科和數學,,語文英語基本沒上過,,,結果就被KK懟回去上文化課了,結果發現好多不好補回來,但是沒有大片的空白,就是那些老師拓展的東西沒有學到,有點虧了,還有作業,基本沒寫,就是偶爾自己閒了就寫寫題當做休息,,這樣怎麼可能提高文化課,,,)這文化課啊,還是要好好學的,畢竟競賽不是上學的唯一途徑,但,高考是。
不瞎扯了來好好看題,,,
題目:
Day 1 題目:
T1: 硬幣求和(scoins)
簡化題目:
求。(答案誤差不超過 )
(就是這麼直接,,)
題解:
(這個題,總用時15分鐘,然後就A掉了,,,神奇吧,,前10分鐘在看題,結果發現,並沒有什麼有用的文字描述,都是些水話,一點都麼沒有用,看到樣例後面的樣例解釋我才差不多能看懂,,然後就試了試大樣例,可以確定是個結論題,然後就直接隨便寫了個結論,結果就A了,,,又一次的小凱的疑惑啊,神奇!)
還是要好好看正解:
要求出來這個答案,可以從 這裏下手。
從高中的放縮得到常見不等式:
可以知道:
從左邊的最小值推出:
從左邊的最大值推出:
再乘上剛開始的,範圍就是:
QED.
但是我考場上吧,直接寫了個 ,然後就A掉了,這個其實是因爲 雖然不在的範圍內,但是可以卡在裏面的,這個自行證明吧。(然後就用掉了我之前攢的所有RP,,)
```
#include<bits/stdc++.h>
#define int long long
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
signed main()
{
// freopen("scoins.in","r",stdin);
// freopen("scoins.out","w",stdout);
int n=read(), c=sqrt(n+1.9);
int ans=0; if(c*c>n+1) ans=1;
printf("%lld\n",c-ans-1);
//自己的一行AC代碼:(從此RP-ocean;)
//int n=read();printf("%lld",sqrt(n)-1);
return 0;
}
T2:漢諾塔(hanoi)
簡化題目:
個盤子個柱子的漢諾塔問題。()
題解:
(這個題,一上去我就知道這個爆搜我是絕對寫不出來的,然後就只寫了的情況,並沒有推出來最終的遞推式,,,結果想着是能拿20分的,結果,,,,,所以,,蒙了,,高精!!!,不是說好了不考高精的嘛!!!我就直接沒有學~~555555555哭死在角落,,,)
正解:
這個是表示你在取個盤子的時候是會把它們放在個柱子上的,然後就在把剩下的盤子放在個柱子上,然後再把之前的盤子放到最後一個柱子上,由於是取個盤子,所以方案數最後要取。
當你找到正確的遞推式之後就會發現這彷彿是一個的寫法,所以呢,要優化啊!當你在取k個盤子的時候你會發現,如果n越大,k就會越大(這一點也可以打表看出來,打出來好像是個斜放的楊輝三角,是LDY大佬發現的,,,tql)然後就符合這個單調性所以就可以用單調指針進行優化。
代碼:
(由於ex的高精,我就寫了主要的那部分代碼,,,)
#include <bits/stdc++.h>
using namespace std;
#define MAXN 1500
#define ll unsigned long long
#define INFLL 0x3f3f3f3f3f3f3f3fllu
#define rint register int
inline int rf(){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;}
int n, m, T; ll f[MAXN+5][MAXN+5];
vector<int> A[MAXN+5];
inline void Mul2(vector<int> &A)
{
A.push_back(0); A[0]*=2;
for(int i=1;i<(int)A.size();i++) A[i]=(A[i]<<1)+A[i-1]/10,A[i-1]%=10;
if(!A.back()) A.pop_back();
}
int main()
{
// freopen("hanoi.in","r",stdin);
// freopen("hanoi.out","w",stdout);
n=MAXN; m=MAXN; A[1].assign(1,2);
for (int i=2; i<=n; ++i)
{
A[i]=A[i-1],--A[i-1][0];
Mul2(A[i]);
}
--A[n][0];
memset(f,0x3f,sizeof f); memset(f[1]+1,0,n<<3);
f[2][1]=1; ll s=2;
for(rint i=1;i<=n;i++)
{
f[3][i]=min(f[3][i],s-1);
s = min(s<<1,INFLL+1);
}
//我碼的在這裏!!(卑微的摻雜在衆多高精處理之中,,,,)
for(int i=4;i<=m;i++)
{
f[i][1]=1;
for(int j=2;j<=n;j++)
{
int k=1;
f[i][j]=2*f[i][k]+f[i-1][j-k];
while (k+1<j&&2*f[i][k]+f[i-1][j-k]>2*f[i][k+1]+f[i-1][j-k-1]) k++;
//這裏用單調指針進行優化,如果k的答案>k+1的答案而且k和k+1存在就k++往下查。
}
}
T=rf();
while(T--)
{
n = rf(); m = rf();
if(m==1){puts("0"); continue;}
else if(m==2){puts(n>1?"No Solution":"1"); continue;}
else if(m==3)
{
for(rint i=A[n].size()-1;~i;i--) putchar(A[n][i]+'0');
putchar('\n');
continue;
}
else printf("%llu\n",f[m][n]);
}
return 0;
}
T3:隨機子樹(tree)
簡化題目:
求在區間的子樹聯通塊的個數。
題解:
(這個題,我由於沒好好看題,沒注重子樹這個東西,然後就直接找的每兩個點的樹上距離,然後就死也想不到怎麼過樣例中的然後就掛掉了,好像調了快兩個小時,,這就告訴我,要好好看題啊~~555555哭死在角落)
正解:
(表示這個題我只會到的做法,還有的方程式優化部分,,就會這麼多,後面要用到長鏈剖分+線段樹+樹形DP的大工程我就不參與了吧,,,這太難了,,等打完CSP再說,,)
由於求直徑大於一個值不太好做,但是直徑小於一個值還是比較好做的,先只考慮直徑在區間的聯通塊個數。這就類似於直徑的DP式了,表示以點p爲最淺的點,最深點距離點p的深度爲d的聯通塊個數。進行樹形DP,將一個新兒子v加入的時候就是:
然後可以想辦把max優化掉,就可以分類討論,枚舉:
這樣就可以用一個前綴和優化就好吧第一個式子搞掉了,但第二個,,,要用線段樹,,菜雞不想寫了,,,,太菜了。。
(說明一下:由於本菜雞太菜,不會自己寫dp時及其解釋,就直接搬題解了,但是在本博客中出現的菜雞是讀懂了的,剩下的就不會了。)
代碼:
(由於不會寫,所以就沒打算訂正代碼,,,,(卑微))
Day1總結:
第一要說的就是看懂題,好好讀懂題,你纔會發現其實你之前的一大堆思想就是個假算法,,就是在瞎扯,,,還是要好好讀題,多推幾遍樣例是不會浪費太多時間的,所以,還是那句話,讀懂題你才知道自己是否在通往得分的路上,否則你一走偏,就從此和OI無緣了,,,還有就是,,要吐槽一下這個高精,,,真的,剛學OI的時候就學到了高精,但老師那個時候就說高精不考不用學了,知道就好了,,所以,我就很聽話的過了一年,,然後到現在就一點也不會高精,也沒有想到會用到高精,這是真的有意思,之前的所有模擬賽都沒有過高精,我還特地去翻了前幾年的真題結果也是沒有高精啊!!!但是打完這場之後老師就又說可能考了,,,~~5555555555哭死o(╥﹏╥)o,,還是抽時間學一下吧。還有就是今天的由於過度使用,估計近期的考試都死掉了吧,,,,(ㄒoㄒ)
Day2題目:
T1:括號序列(bracket)
簡化題目:
給你一個括號串,求出合法的括號子串的個數。
題解:
(這個題啊,剛看懂題就大驚了,這不就是2017年noip的原題《時間複雜度》的極簡版嗎,而且是沒有REE的情況,就是開個棧就好了,,,但是,,(ㄒoㄒ)一個莫名其妙的原因,直接掛掉了,不知道爲什麼,結果是少考慮了一種情況,然後就完了,,100分啊,,沒有了,,這要是正式考試,我就只能死回去學文化課了,,,o(╥﹏╥)o)
正解:
神奇的是這個題,,,竟然有的優化型爆搜,的lower_bound的神奇做法,真的是強啊~~
這個題確實就是一個棧就跑過了,開個棧,如果是"("就進棧,不是就判斷,如果這個前面什麼都沒的話就加上 (自身的貢獻),不是的話就加上之前的貢獻,再加上自身的貢獻 ,對於括號只有兩種可能,一種是嵌套的,一種就是就地並列的,所以可以對每個單括號寫第二個結構體,每次可以記錄一下嵌套的個數,這樣的話方便統計答案。
代碼:
#include<bits/stdc++.h>
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
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;
char ch[sea],s[sea];
int n,top=0,ans,b[sea];
struct hit{int x,sum;}a;
stack<hit>st;
int main()
{
// freopen("bracket.in","r",stdin);
// freopen("bracket.out","w",stdout);
cin>>ch; n=strlen(ch);
over(i,1,n) s[i]=ch[i-1]; int num=ans=0;
over(i,1,n)
{
if(s[i]=='(') a.x=1;else a.x=2;
if(st.size())
{
if(st.top().x==1&&a.x==2)
{
st.pop();
if(st.empty()) ans+=++num;//記錄並列的
else ans+=++st.top().sum;//記錄嵌套的
}
else st.push(a);
}
else st.push(a);
}
printf("%d\n",ans);
return 0;
}
T2:和數檢測(check)
簡化題目:
給定個詢問,個數,常數,求是否存在。
題解:
(這個題,上去一看,暴力跑啊,只有分,,那就開個桶跑,,只有,,,那就,只有分,那就二分,,也是,那就,,就分段單調指針跑二分,也就分,,,然而這個題我跑的,就拿了,,,但是還有的PHarr大佬竟然用直接就的的,,,tql)
正解:
分塊!!!
這題解也是真的妙,這是真的神奇,值域分塊,我真的是,,,服了,,還是序列分塊寫多了,值域分塊都忘的這麼幹淨了,這個題暴力來說就是開個桶啊,但是對於進行分開真的是有意思,(這裏說明一下,其實真實的m不是,在機房有位Tyouchie大佬寫的樹狀數組,寫的是的,證明了所有數據在之前就能找到答案,所以這個真實的數據並不是)。
這就來解釋一下的標算,(由於真的是很不能看懂,覺得出題人是用文言寫的吧,所以就自己看代碼理解了一下,由於自己的分塊還是比較熟悉的就比較好看懂)
首先 這就是塊長,然後這就是一個已經分過塊的桶,這其實就是叫值域分塊。
在每個塊裏面進行對這個數的存桶,然後怎麼優化呢,就是把每個塊進行取模這樣的話,每個塊就是,然後對於每個數再進行取模等一下代碼細節,具體看代碼吧,應該很好理解。
代碼:
#include<bits/stdc++.h>
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
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 pool=1e6+7;
const int ocean=1e9;
const int sea=4e4;
vector<int>v[sea];
typedef vector<int>::iterator iter;
int T,n,m,flag,a[pool],block,f[sea*4];
int main()
{
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
T=read();
over(t,1,T)
{
n=read(); m=read();block=m/sea;
over(i,0,block) v[i].clear(); int ans=0;
over(i,1,n)
{
int x=read(); int y=x%sea; x=x/sea;
v[x].push_back(y);
}
over(i,0,block)
{
//先放進去a[i]
int l=(m-(i+1)*sea+1)/sea,r=(m-i*sea)/sea,base=l*sea;
over(j,l,r) for(iter it=v[j].begin();it!=v[j].end();it++)
f[*it+j*sea-base]=1;
//對比
for(iter it=v[i].begin();it!=v[i].end();it++)
if(f[m-*it-i*sea-base]) {ans=1;break;}
//清空
over(j,l,r) for(iter it=v[j].begin();it!=v[j].end();it++)
f[*it+j*sea-base]=0;
if(ans) break;
}
printf("%d\n",ans);
}
return 0;
}
T3:與(and)
簡化題目:
給你n個數,求你有多少種方法可以將它分成兩部分,使這兩部分至少有一個數,並且兩部分進行按位&操作後的結果是相同的。
題解:
(這個題我連指數級的暴力都打不出來,,,~~555555,還是之後又找到的大佬學了指數級的爆搜,由於指數級的爆搜不會,就直接想的正解,但是,正解又沒有完好的思路體系,所以就直接放棄了,,,)
這個題Lcentury以及他的同學,用成功掉,,Lec是寫了個的,然而他同學就更厲害了,,直接用寫的,,就50行+,,,,,而我。。。還在想是怎麼掉的。。
正解:
容斥+並查集!!!
這個題啊,題解是真的妙!!!
首先這個題由於要求方案數,仔細看一下題,如果正這去做的話,就是一個DP的東西,去找到相同的,這樣子不會太好做,所以就用到了容斥,有所有的減去不相同的,由於你是要求有集合的一個和,也可以理解成並集,但是你可以求出每個集合,用容斥來做,這點可以參考一下下面的解釋,這樣的話答案就可以表示爲:
這個其中的就是對於這個數限制,關於這個限制還是要好好說一下的,就是呢,你對於這個數的二進制位從最右邊開始進行遍歷,如果他是或者他在這個限制內的數很符合這個限制的話,就記錄他的答案,這就需要我們先把這個限制的滿位遍歷出來,然後在對於每一個限制進行對於每一個數的遍歷,找到在這個限制內的所有的合法數,這一點可以用並查集進行合併,爲什麼用並查集進行合併呢,這裏呢,我可以舉個例子進行說明,當你已經限制了兩位的時候就是的時候,就可以有三種情況:,這樣的情況中可以知道,& 或 & 或 & 或 & 在這個限制中都是不相同的數,只有& 是對於這一限制有貢獻的,而由於現在要找到所有沒有貢獻的數,那就可以開個並查集對於像這樣的數進行一個合併,用並查集來做就很好。這所有都做完,是把都做一遍,這樣的話最後就是查找一下並查集的個數就是,然後在減去你多加上的兩種不合法情況就是 了,至於爲什麼是的次方,這個是組合數的基礎知識,不知道的也可以打表找出來。最後對答案加上容斥的部分再進行合併即可。
細說一下這個容斥:
設S是有窮集, 是n條性質。S中的任一元素x對於這n條性質可能符合其中的1種,2種,3種……n種,也可能都不符合。設 表示S種具有 性質的元素構成的子集。有限集合A中的元素個數記爲。則:
S中具有性質 的元素個數爲:
就是對於一大推要求和的集合,可以轉化爲求它們的交集,可以參考下下面的圖:
這樣的話就好理解多了,你需要求的就是所有藍色面積的和,但是你只知道 這就可以通過一加一減的形式,減去重複的加上新增的,再加上之前的式子,這就很好理解了。
代碼:
指數級爆搜:
#include<bits/stdc++.h>
using namespace std;
const int nn=100;
int n, ans=0, a[nn];
bool vis[nn];
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
void dfs(int now,int sum)//表示第now個數,選擇了sum個數
{
if(now==n+1)//由於數now的初值的 1,則最大就是n+1,
{
if(sum==0||sum==n) return ;//如果兩個集合中存在沒有選擇的數
int x=(1<<18)-1, y=(1<<18)-1;//全賦爲 1
for(int i=1;i<=n;++i) if(vis[i]) x=x&a[i];else y=y&a[i];//判斷兩個集合內的答案是否是一樣的
if(x==y) ans++;
return ;
}
dfs(now+1,sum); vis[now]=1;//不選擇這個數
dfs(now+1,sum+1); vis[now]=0;//選擇這個數
return ;
}
int main()
{
// freopen("and.in","r",stdin);
// freopen("and.out","w",stdout);
n=read(); for(int i=1;i<=n;++i) a[i]=read();
dfs(1,0);
printf("%d\n",ans);
return 0;
}
正解:
#include<bits/stdc++.h>
#define int long long
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
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=61;
const int ocean=131072;
int n,ans=0,a[sea],b[sea],fa[sea];
int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
signed main()
{
// freopen("and.in","r",stdin);
// freopen("and.out","w",stdout);
n=read(); over(i,1,n) a[i]=read();
b[0]=1; over(i,1,sea) b[i]=b[i-1]*2;
over(d,0,ocean-1)
{
int d1=d,cnt=1,s;//容斥
over(i,1,n) fa[i]=i;
for(int i=0;i<17;i++,(d1>>=1),s=i)//枚舉d的位數
if(d1&1)
{
cnt=-cnt;//容斥
int j1=(int)b[i],j2=0,flag=0;//j1的初始值就是i滿位的時候
over(j,1,n) //枚舉所有的數
if(!(a[j]&j1))//如果這個數沒有超過滿位的j1
{
flag=1;//標記一下,如果是有的數大於現在的限制了,就直接擴大限制而不必是僅僅跳出次循環了
if(!j2) j2=get(j);//向上找到他的父親節點,就是在比他位數少的數中有1的
else fa[get(j)]=j2;//要是之前都沒有就把他直接賦成根節點,這樣就會得到好多並查集
}
if(!flag) break;
}
if(s!=17) continue;
int num=0; over(i,1,n) if(i==get(i)) num++; //記錄並查集個數
ans+=cnt*(b[num]-2);
//答案就是所有位數上的(-1)^cnt*(2^k-2);具體的-2是因爲你在枚舉的時候並不能全部枚舉完,要刪除兩種情況,就是枚舉所有和枚舉0種的情況
}
printf("%lld\n",ans);
return 0;
}
Day2總結:
恕我直言,今天的題解,真的是巧妙啊,值域分塊,並查集做序列操作,真的是強,而且特別考驗對普通序列的儘可能優化,頭腦風暴啊,tql,%%%%%%出題人。但是呢,我的T1是打掛了,,這就太慘了,儘管寫過原題,可能是對原題還不是特別透徹吧,需要再研究研究多寫幾遍,T2是太ex了,std在我本機上跑的,在大佬電腦刪跑的,不知爲何,由於沒有給是時限,是看自己評測機上的時間,老師開了兩倍時限,我在可我還是死掉了,,,後來改了改才過,,但是這個想法是真的沒想到,桶還能這這樣開,,T3告訴我指數級(階乘級)暴力很重要,不要忽略,之前都是說說,但是沒寫過,還沒練過,然後就掛掉了。總之,今天是個頭腦風暴但暴力主場的不知怎麼說的比賽,所以還是那句話:一個問題要有兩種想法,一種保底暴力,一種天馬行空。
總結:
這兩天的模擬賽打下來是150,要是D2T1不打掛就250了,然後就是在打指數級暴力就是270了,大概就接近省一了,,但是說實話,兩天大概都能拿個150—180左右纔可以穩穩的啊,這就很難了我還是菜,儘管就差不到60天了,我還是要好好努力的!!!