S2考前綜合刷題營Day3
歡樂
【問題描述】
你是能看到第一題的 \(friends\) 呢。
——hja
眾所周知,小蔥同學擅長計算,尤其擅長計算組合數,但這個題和組合數沒什麼關係。
給定 \(N\),求一個 \(K\),使得 \(K!≥\frac{N!}{K!}\) 並且 \(K\) 儘量小。
【輸入格式】
一行一個整數 \(N\)。
【輸出格式】
一行一個整數代表答案。
【樣例輸入】
10
【樣例輸出】
7
【資料規模與約定】
對於 \(30\%\) 的資料,\(N≤10\)。
對於 \(60\%\) 的資料,\(N≤100\)。
對於 \(80\%\) 的資料,\(N≤1000\)。
對於 \(100\%\) 的資料,\(3≤N≤10^6\)
Solution
80pts
列舉 \(k\),利用高精算出 \(k!\) 和 \(\frac{N!}{K!}\),然後求答案即可。
時間複雜度 \(O(n^2)\)。
100pts
比較兩個數的大小不一定要用原數的大小來比較,我們可以將它們同時取 \(log\) 。
所求式子就變成了:\(\log{K!}>=\log{\frac{N!}{K!}}\),利用 \(log\) 函式的性質,式子可以進一步化簡:
\(\log{K!}>=\log{N!}-\log{K!}\),且有 \(\log{N!}=\log{1}+\log{2}+...+\log{N}\)。
時間複雜度 \(O(n)\)
#include<iostream> #include<cstdio> #include<cmath> using namespace std; const int N=1e6+5; int n,ans; double Log[N],S[N]; bool check(int x) { return 2.0*S[x]>S[n]; } int main() { cin>>n; for(int i=1;i<=n;i++) { Log[i]=(double)log(i)/log(2); S[i]=S[i-1]+Log[i]; } int l=0,r=n+1; while(l<=r) { int mid=(l+r)>>1; if(check(mid)) ans=mid,r=mid-1; else l=mid+1; } printf("%d\n",ans); return 0; }
水題
【問題描述】
你是能看到第二題的 \(friends\) 呢。
——aoao
眾所周知,小蔥同學擅長計算,尤其擅長計算組合數,但這個題和組合數沒什麼關係。
現在有若干個任務,每個任務需要一定的執行時間,以及要求其依賴的所有任務執行完成後才能夠執行。你可以同時做多個任務,但是一個任務一旦開始就不能停下必須做完。如果當前有多個任務可以做,按照這些任務到來的時間作為第一關鍵字,任務的名字作為第二關鍵字選擇最小的任務執行。問執行完所有任務所需要的時間是多少。
【輸入格式】
一行一個字串。
對於每個任務,其格式為“任務名字:[依賴任務\(1\),依賴任務\(2\),……,依賴任務\(k\)]:執行時間”。不同任務與不同任務之間用分號分割。在輸入完所有任務之後,會用“/”隔開一個整數,該整數代表同一時間最多執行多少個任務。
【輸出格式】
一行一個整數代表答案。
【樣例輸入】
b:[a]:2;c:[a]:3;a:[]:1/2
【樣例輸出】
4
【資料規模與約定】
對於 \(30\%\) 的資料,任務數量 \(≤10\)。
對於 \(60\%\) 的資料,任務數量 \(≤100\)。
對於 \(100\%\) 的資料,任務數量 \(≤1000\),任務的名字長度小於等於 \(50\),所需要的執行時間不超過 \(100\),總依賴數量不超過 \(200000\)。
Solution
其實這道題就是讀入噁心,思路就是按照 \(topo\) 排序依次做任務即可。
可以先數一數有多少個分號,進而確定有多少個點。
然後再把所有點的名字處理出來,然後再處理點之間的依賴關係。
要注意的細節還是很多的。
#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
struct node1{int y,next;}b[20001];
struct node2{int id,time;};
int sta[10001],lenb;
int a[10001],v[10001],rd[10001],js;
char name[10001][52];int lenname[10001];
char yl[200001][52];
int jsyl,begin1[10001],end1[10001];
int lenyl[200001];
int s[10001];
int rws;
bool operator <(struct node2 a1,struct node2 a2)
{
if(a1.time!=a2.time)return a1.time>a2.time;
else return strcmp(name[a1.id],name[a2.id])>0;
}
priority_queue <node2> q;
priority_queue <node2> d;
int lend1;
int read(){
char ch;
while(1){
++js;
while((ch=getchar())!=EOF&&ch!=':')name[js][lenname[js]++]=ch;
name[js][lenname[js]]='\0';
getchar();
begin1[js]=jsyl+1;
ch=0;
while(ch!=']')
{
jsyl++;
while((ch=getchar())!=EOF&&ch!=','&&ch!=']')yl[jsyl][lenyl[jsyl]++]=ch;
yl[jsyl][lenyl[jsyl]]='\0';
if(lenyl[jsyl]==0)jsyl--;
}
end1[js]=jsyl;
getchar();
int zf=0;
if((ch=getchar())=='-')zf=1;else s[js]=(ch^48);
while((ch=getchar())>='0'&&ch<='9')s[js]=(s[js]<<3)+(s[js]<<1)+(ch^48);
if(zf)s[js]=(-s[js]);
if(ch=='/')
{
int zf=0;
if((ch=getchar())=='-')zf=1;else rws=(ch^48);
while((ch=getchar())>='0'&&ch<='9')rws=(rws<<3)+(rws<<1)+(ch^48);
if(zf)rws=(-rws);
return 0;
}
}
}
int merge(int x,int y)
{
b[++lenb].y=y;
b[lenb].next=sta[x];
sta[x]=lenb;
rd[y]++;
return 0;
}
int main()
{
read();
for(int i=1;i<=js;i++)
{
for(int j=begin1[i];j<=end1[i];j++)
{
for(int h=1;h<=js;h++)if(strcmp(name[h],yl[j])==0)merge(h,i);
}
}
for(int i=1;i<=js;i++)if(rd[i]==0)
{
node2 p=(node2){i,0};
q.push(p);
}
while(!q.empty()&&lend1<rws)
{
node2 p=q.top();
p.time+=s[p.id];
d.push(p);
q.pop();
lend1++;
}
int now=0;
while(!d.empty())
{
now=d.top().time;
while(!d.empty()&&d.top().time<=now)
{
node2 p=d.top();
d.pop();
lend1--;
for(int i=sta[p.id];i;i=b[i].next)
{
rd[b[i].y]--;
if(rd[b[i].y]==0)q.push((node2){b[i].y,now});
}
}
while(!q.empty()&&lend1<rws)
{
node2 p=q.top();
q.pop();
lend1++;
p.time=now+s[p.id];
d.push(p);
}
}
printf("%d",now);
return 0;
}
模擬
【問題描述】
你是能看到第三題的 \(friends\) 呢。
——laekov
眾所周知,小蔥同學擅長計算,尤其擅長計算組合數,但這個題和組合數沒什麼關係。
給定 \(N\) 個長度不超過 \(50\) 的 \(01\) 字串,你可以將其不斷重複直到長度為 \(50!\)。現在要求輸出從 \(1∼50!\) 中有多少個位置,在這個位置上所有字串 \(1\) 的個數加起來等於 \(i\)。
【輸入格式】
第一行一個整數 \(N\) 代表字串個數。
接下來 \(N\) 行每行一個字串。
【輸出格式】
輸出 \(N+1\) 行,其中第 \(i\) 行代表 \(1\) 的個數為 \(i−1\) 的位置的個數對 \(10^9+7\) 取模之後的結果。
【樣例輸入】
1
1
【樣例輸出】
0
318608048
【資料規模與約定】
對於 \(20\%\) 的資料,\(N=1\)。
對於 \(40\%\) 的資料,\(N≤10\)。
對於另外 \(20\%\) 的資料,所有字串長度均為質數。
對於另外 \(20\%\) 的資料,所有字串長度小於等於 \(10\)。
對於 \(100\%\) 的資料,\(1≤N≤100\),我覺得原來的第三題太簡單了,所以臨時換了這個題。
Solution
先理解一下題意:給出 \(n\) 個字串,將它們都寫至 \(50!\) 的長度,那麼就產生了 \(50!\) 個位置,現在要問有多少個位置有 \(0\) 個 \(1\),有多少位置有 \(1\) 個 \(1...\),有多少位置有 \(n\) 個 \(1\)。
20pts
\(n=1\)
也就是說只有一個字串,我們就考慮這一個字串有多少個位置有 \(0\) 個 \(1\),有多少個位置有 \(1\) 個 \(1\)。
假如給的字串是 \(10010\),也就是說每五個位置就會出現 \(3\) 個 \(0\) 和兩個 \(1\),那麼就有 \(3*\frac{50!}{5}\) 個位置是 \(0\),有 \(2*\frac{50!}{5}\) 個位置是 \(1\)。
40pts
所有字串長度小於等於 \(10\)。
我們可以將所有的字串先寫到 \(2520\) 的長度\((1~10\)的最小公倍數\()\),這樣每 \(2520\) 個就會出現一次迴圈,我們只需要暴力地求出前 \(2520\) 箇中有 \(0\) 個 \(1\) 的位置有幾個,有 \(1\) 個 \(1\) 的位置有幾個...,然後再乘 \(\frac{50!}{2520}\) 就是對應的答案了。
貌似可以拿 \(60pts\)。
80pts
所有字串的長度均為質數。
只有這一部分分的做法與正解有關係。
假設我們有兩個串 \(010\) 和 \(10011\),我們還是先擴充套件到他們的最小公倍數的長度:
我們發現,第一個字串的第一位分別與第二個字串的每一位都對應了一次。
以此類推,第一個字串的每一位都會與第二個字串的每一位對應一次。
由於我們並不關心字串本身長什麼樣,我們只關心有多少個位置出現了 \(0\) 個 \(1\)...所以我們可以把字串拆成以下形式:
\(010=2\)個\(0+1\)個\(1\)
\(10011=2\)個\(0+3\)個\(1\)
那麼它們組合起來的結果是什麼呢?類似於多項式乘法:
\((2\)個\(0+1\)個\(1)*(2\)個\(0+3\)個\(1)=4\)個\(0+8\)個\(1+3\)個\(2\)
假如我們有兩個長度為 \(5\) 的字串:\(10011\) 和 \(01011\),我們可以將它合併為:\(11022(1\)個\(0+2\)個\(1+2\)個\(2)\)。
這樣我們就可以保證每個長度的字串只有一個。
由於每個字串的長度是互質的,所以我們將每個長度的字串的表示式寫出來,做一個乘法,問題就解決了。
100pts
由於此刻字串的長度不互質,所以一個字串的每一位不能和其他字串的每一位都對應了。
例如有一個長度為 \(4\) 和一個長度為 \(6\) 的字串,它們的最小公倍數是 \(12\),可以發現長度為 \(4\) 的字串的每一位只與第二個字串的 \(3\) 個位置對應。
不互質的情況能否轉化為互質的情況呢?
我們先求出 \(\gcd(4,6)=2\),我們可以將長度為 \(4\) 的字串分成 \(2\) 塊,把長度為 \(6\) 的字串分成 \(3\) 塊,它們每一塊的長度都為 \(2\)。
會發現,現在並不是每一個位置會與下面進行對應,而是每一塊會與下面的每一塊進行對應。(顯然兩個字串的塊數互質,這樣就轉化成了上面部分分的情況)
由於塊是整齊對應的,所以上面字串的第一個塊的第一個位置會與下面每個塊的第一個位置對應,上面字串的第一個塊的第二個位置會與下面每個塊的第二個位置對應...
所以我們可以對下面的字串的每一個塊的每一個位置算一遍答案,即將所有塊的第一個位置算一遍答案,所有塊的第二個位置算一遍答案...
還是舉個例子,我們有一個長度為 \(4\) 的字串 \(1001\) 和一個長度為 \(6\) 的字串 \(010011\):
長度為 \(4\) 的字串被分成了兩塊:\(10\) 和 \(01\),長度為 \(6\) 的字串被分成了三塊:\(01,00\) 和 \(11\)。
根據上面討論的結果,我們知道上面每一塊的第一個位置 \(1\) 和 \(0\),分別與下面每一塊的第一個位置:\(0,0\) 和 \(1\) 做一次對應。
貢獻相當於:\(10×001=(1\)個\(0+1\)個\(1)*(2\)個\(0+1\)個\(1)=2\)個\(0+3\)個\(1+1\)個\(2\)。
同理:上面每一塊的第二個位置 \(0\) 和 \(1\),分別與下面每一塊的第二個位置:\(1,0\) 和 \(1\) 做一次對應:
貢獻相當於:\(01×101=(1\)個\(0+1\)個\(1)*(1\)個\(0+2\)個\(1)=1\)個\(0+3\)個\(1+2\)個\(2\)。
答案就是每個位置的貢獻之和。
但是當字串一多,你每次兩兩合併的時候最大公因數會比較大,所以時間複雜度也會升高。
好像我們漏掉了一個非常非常重要的性質:字串的長度小於等於 \(50\),沒有這個性質還真的做不了。
由於 \(\sqrt{50}=7\),所以我們可以把 \(50\) 以內的質數分成兩部分,一部分是小於等於 \(7\) 的,有:\(2,3,5,7\);還有一部分是大於 \(7\) 的,有:\(11,13,17,19,23,29,31,37,41,43,47\)。
根據唯一分解定理可以知道:由於所有的字串長度小於等於 \(50\),所以它們的長度都是在這裡邊找幾個數乘起來。
考慮到我們的優化方式只有兩種:
一種是你可以找到一個好的合併順序,使得每次合併的最大公因數都不會太大;
另一種就是你可以令每次合併的最大公因數都是一樣的,這樣我們就可以一次把它們全都合併了。
那要怎麼找呢?
發現 \(2\) 在 \(50\) 以內的若干次冪最大是 \(32\),\(3\) 的話是 \(27\),\(5\) 的話是 \(25\),\(7\) 的話是 \(49\)。
考慮一種情況,如果所有字串的長度的質因子只有 \(2,3,5,7\) 的話,我們可以將它們都擴充套件為 \(32*27*25*49\) 的長度。
那如果不只含有 \(2,3,5,7\) 這四個因子呢?那它一定包含大於 \(7\) 的因子中的其中一個,且只能包含一個。
假設包含的這個質因子為 \(p\),那麼它還可能包含一些小於 \(7\) 的質因子。
由於 \(p\) 最小是 \(11\),也就是說,我們只可能包含 \(2,3\) 這兩個因子了,而且在 \(50\) 內最多隻能將其擴充套件為 \(2p,3p,4p\)。
由於 \(2,3,4\) 的最小公倍數是 \(12\),所以我們可以將其擴充套件為 \(12p\),這樣一定保證是原字串的倍數長度。
由於每個字串都被擴充套件到了 \(12p\),由於 \(p\) 這個因子是上面字串所沒有的,那麼它們的最大公因子一定是 \(12\)。
所以我們把每個字串的每 \(12\) 個位置分成一組,對於每個位置做一遍 \(dp\),一共 \(12\) 次 \(dp\),那麼這個題就做完了。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define inc(a,b) {a+=b;if (a>=mo) a-=mo;}
const int maxn=1010;
const int mo=1000000007;
const int maxb=32*27*25*49;
const int prime[]={0,11,13,17,19,23,29,31,37,41,43,47,0};
int n,num[maxb+10],res[maxn][12],resx[maxn][12],ans[maxn],l[maxn];
char s[maxn][maxn];
int main()
{
scanf("%d",&n);
for (int a=1;a<=n;a++)
{
scanf("%s",s[a]+1);
l[a]=strlen(s[a]+1);
}
for (int a=1;a<=n;a++)
if (maxb%l[a]==0)
for (int b=1,c=1;b<=maxb;b++,c++)
{
if (c>l[a]) c=1;
if (s[a][c]=='1') num[b]++;
}
for (int a=1;a<=maxb;a++)
inc(res[num[a]][a%12],1);
for (int a=1;prime[a];a++)
{
int v=prime[a];
memset(num,0,sizeof(num));
for (int b=1;b<=n;b++)
if (l[b]%v==0)
{
for (int c=1,d=1;c<=v*12;c++,d++)
{
if (d==l[b]+1) d=1;
if (s[b][d]=='1') num[c]++;
}
}
memset(resx,0,sizeof(resx));
for (int b=0;b<=n;b++)
for (int c=1;c<=v*12;c++)
inc(resx[b+num[c]][c%12],res[b][c%12]);
for (int b=0;b<=n;b++)
for (int c=0;c<12;c++)
res[b][c]=resx[b][c];
}
for (int a=0;a<=n;a++)
{
for (int b=0;b<12;b++)
inc(ans[a],res[a][b]);
ans[a]%=mo;
}
int mul=1;
for (int a=1;a<=50;a++)
{
if (a==32 || a==27 || a==25 || a==49) continue;
bool able=true;
for (int b=1;prime[b];b++)
if (prime[b]==a) able=false;
if (!able) continue;
mul=1ll*mul*a%mo;
}
for (int a=0;a<=n;a++)
printf("%d\n",(int)(1ll*ans[a]*mul%mo));
return 0;
}
賽
【問題描述】
你是能看到第四題的 \(friends\) 呢。
——laekov
眾所周知,小蔥同學擅長計算,尤其擅長計算組合數,但這個題和組合數沒什麼關係。
定義函式 \(f\) 為計算序列 \(V\) 字的數量的函式。定義 \(V\) 字為 \(i<j<k,a_i>a_j,a_k>a_j\) 的三元組 \((i,j,k)\) 。現在給定 \(N\) 個數 \(a_1,a_2,⋯,a_N\),求
\(\sum_{l=1}^{n}\sum_{r=l}^{n}f(a_l,a_{l+1},⋯,a_r)\)
【輸入格式】
第一行一個整數 \(N\)。
接下來一行 \(N\) 個整數。
【輸出格式】
一行一個整數代表答案對 \(10^9+7\) 取模之後的答案。
【樣例輸入】
5
2 3 1 4 5
【樣例輸出】
9
【資料範圍與規定】
對於 \(30\%\) 的資料,\(N≤20\)。
對於 \(60\%\) 的資料,\(N≤100\)。
對於 \(80\%\) 的資料,\(N≤1000\)。
對於 \(100\%\) 的資料,\(1≤N≤10^5,1≤a_i≤N\),我本來想出 \(W\) 的形狀而非 \(V\) 的形狀來著的。
Solution
60pts
我們按照題目中所說的去列舉 \(l,r,i,j,k\) 然後判斷是否為三元組即可。
時間複雜度 \(O(n^5)\)。
80pts
假如 \(a_i,a_j,a_k\) 能構成一個三元組,那麼這個三元組包含於 \(i*(n-k+1)\) 個區間。
假如 \(j\) 的左邊一共有 \(p\) 個數 \(a_{i_1},a_{i_2}...a_{i_p}\) 是大於 \(a_j\) 的,右邊共有 \(q\) 個數 \(a_{k_1},a_{k_2}...a_{k_q}\) 是大於 \(a_j\) 的,我們固定 \(a_{i_1}\),則此時的貢獻為 \(i_{1}*\sum_{b=1}^{q}(n-k_b+1)\),如果固定 \(a_{i_2}\) 貢獻就是 \(i_{2}*\sum_{b=1}^{q}(n-k_b+1)\),那麼總貢獻就是 \(\sum_{c=1}^{p}i_c*\sum_{b=1}^{q}(n-k_b+1)\)。
所以我們可以列舉 \(a_j\) 左邊的數的符合條件的數的下標和,列舉右邊符合條件的數的下標,算出答案。
時間複雜度 \(O(n^2)\) 。
100pts
我們可以用權值線段樹或樹狀陣列來求下標和。
時間複雜度 \(O(n\log{n})\)
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
const ll N=1e5+5;
const ll M=1e9+7;
ll n,top;
ll a[N],L[N],R[N],sum[N<<2],times[N<<2],ans;
//權值線段樹維護下標和sum和出現次數times,
void update(ll node)
{
sum[node]=sum[node<<1]+sum[node<<1|1];
times[node]=times[node<<1]+times[node<<1|1];
}
void insert(ll node,ll l,ll r,ll x,ll k)
{
if(l==r)
{
sum[node]+=k; //目前所有大小為l的數的下標和
times[node]++; //大小為l的數又出現了一次
return ;
}
ll mid=(l+r)>>1;
if(x<=mid) insert(node<<1,l,mid,x,k);
else insert(node<<1|1,mid+1,r,x,k);
update(node);
}
ll query1(ll node,ll l,ll r,ll x,ll y) //詢問1返回下標和
{
if(l>r) return 0;
if(x<=l&&r<=y) return sum[node];
ll mid=(l+r)>>1;
ll cnt=0;
if(x<=mid) cnt+=query1(node<<1,l,mid,x,y);
if(y>mid) cnt+=query1(node<<1|1,mid+1,r,x,y);
return cnt;
}
ll query2(ll node,ll l,ll r,ll x,ll y) //詢問2返回出現次數
{
if(l>r) return 0;
if(x<=l&&r<=y) return times[node];
ll mid=(l+r)>>1;
ll cnt=0;
if(x<=mid) cnt+=query2(node<<1,l,mid,x,y);
if(y>mid) cnt+=query2(node<<1|1,mid+1,r,x,y);
return cnt;
}
int main()
{
scanf("%d",&n);
for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
for(ll i=1;i<=n;i++) //求出每個數左邊比它大的數的下標和
{
L[i]=query1(1,1,n,a[i]+1,n); //查詢範圍是a[i]+1~n
insert(1,1,n,a[i],i);
}
memset(sum,0,sizeof(sum));
memset(times,0,sizeof(times));
for(ll i=n;i>=1;i--) //求右邊比它大的數,就要從右邊開始列舉
{
ll x=query1(1,1,n,a[i]+1,n);
ll y=query2(1,1,n,a[i]+1,n);
R[i]=y*(n+1)-x;
insert(1,1,n,a[i],i);
}
for(ll i=1;i<=n;i++)
ans=(ans+L[i]*R[i]%M)%M;
printf("%lld\n",ans%M);
return 0;
}