1. 程式人生 > >紀念神九發射作業(2)

紀念神九發射作業(2)

操作 如果 amp space 直接 放棄 getch tchar 修改

昨天在rabbithu的幫助下又做了兩道題……不過這debug的過程可真是心酸……

B題:JXOI2018 遊戲

這道題它看上去似乎是一道序列問題但它實際上是一道赤裸裸的數學題。

我們 把問題簡化一下,就是要求在l~r的所有全排列中,對於任意一個排列進行線性篩,每篩一個數的同時篩掉其所有的倍數,記篩掉一個排列中所有數要用的時間為ti,結果求ti總和mod 1e9+7.

首先思考最關鍵的問題,如何統計對於任意一個排列,把所有數篩完要多長時間?

在這個區間之內,每篩去一個數都會篩去它的倍數,而在這個區間內沒有因子的數字就必定不會被其他數篩去,也就是說,對於給定的區間l~r,必然存在固定的s個數必須要被篩去一次。我們將這s個數稱為關鍵數,那麽對於一個排列,最後一個關鍵數出現的位置即為所要篩完這個區間所用的時間。

如何確定關鍵數呢?考慮一下線性篩的性質。歐拉篩法每次都用一個數的最大因子和最小質因子篩去它,那麽我們發現,除了所有的質數是關鍵數,任意一個數,如果其最大因子不在這個序列範圍內,那麽它也是一個關鍵數。(因為無法被其他數篩去)

這裏要特別註意一點,那就是如果區間包括1的話,那麽就只剩下1這一個關鍵數了。(當時沒發現WA的我淚流滿面,感謝rabbithu debug……)這樣我們線性篩一遍就可以確定關鍵數了。

之後就轉化為了一個組合數問題!

我們可以枚舉最後一個關鍵數出現的位置。(當然區間長度小於s可以跳過……)這樣的話,如果在最後一個關鍵數出現之前,區間長為t,那麽後面長為n-t的區間就可以任意放入n-s個數,就是(排列符號不會打orz……)A(n-s,n-t)。

但是還沒結束,我們在考慮前面區間,這裏不能簡單的認為再乘以一個t!就可以了,因為這樣無法保證最後一個關鍵數的位置放了一個關鍵數。那麽我們把那一位單獨提取,有s中可能,前面t-1位所對應的自然就是(t-1)!了。

把這三個數乘起來再做累加即可,記得要多取模(反正不卡常)。階乘和組合數率先用逆元處理即可。

上代碼。

// luogu-judger-enable-o2
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm> 
#include<cmath>
#include
<queue> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar(‘\n‘) using namespace std; const int M = 1e7+5; const int MOD = 1e9+7; typedef long long ll; ll read() { ll ans = 0,op = 1; char ch = getchar(); while(ch < 0 || ch > 9) { if(ch == -) op = -1; ch = getchar(); } while(ch >= 0 && ch <= 9) { ans *= 10; ans += ch - 0; ch = getchar(); } return ans * op; } ll l,r,n,p[M],ind[M],sum[M],inv[M],ans,s; bool np[M]; void euler() { np[1] = 1;ind[1] = 1; rep(i,2,r)//線性篩 { if(!np[i]) p[++p[0]] = i,ind[i] = 1;//壓入素數隊列,素數一定是關鍵數 for(int j = 1;p[j] * i <= r;j++) { np[p[j]*i] = 1;//篩掉一個 if(i < l) ind[p[j]*i] = 1;//記錄關鍵數 if(!(i % p[j])) { break;//保證每個數都被最大因子篩去 } } } } ll ksm(ll a,ll b) { ll q = 1; while(b) { if(b&1) q *= a,q %= MOD; a *= a,a %= MOD; b >>= 1; } return q % MOD; } void init() { sum[0] = 1;inv[0] = 1; rep(i,1,r) sum[i] = sum[i-1] * i % MOD; inv[r] = ksm(sum[r],MOD-2); per(i,r,2) inv[i-1] = inv[i] * i % MOD; rep(i,l,r) if(ind[i]) s++; } ll calc(int s,int t) { return sum[n-s] * inv[t-s] % MOD * s % MOD * sum[t-1] % MOD * t % MOD; } int main() { l = read(),r = read();n = r-l+1; if(l != 1) euler(); else s = 1; init(); rep(i,l+s-1,r) { ll t = i-l+1; ans += calc(s,t); ans %= MOD; } printf("%lld\n",ans); return 0; }

D題 六省聯考2017 期末考試

聽說這是一道非常水的題……但是我智商太低想不出來……

而且……這個題的debug經歷給我的啟示就是……沒想好之前別瞎寫……浪費的時間太多了……而且最後無可奈何只能放棄lowerbound做法,用rabbithu的做法A掉這道題……

好……言歸正傳。閱讀題目之後可以發現,學生的等待時間與順序無關,老師閱卷時間也與順序無關(同樣的修改操作也不影響時間),最終影響到學生等待和因為修改操作而導致的問題都與最晚發布成績的時間有關,因此,我們選擇枚舉最晚發布成績的時間。

確定這個思路之後,我們就要計算一下對於任意一個時間所產生的不愉快度。如何計算呢?因為我們枚舉最晚公布成績的時間,所以對於任意一個枚舉的時間,我們都可以用修改操作把原來晚於這個時間的公布成績時間修改到這個時間。(這裏需要保證沒有負面影響,即使a操作代價更大也不行,因為我們後面還會再一次枚舉更晚公布成績的時間)因為我們要求最小值,那麽如果a操作(沒有負面影響)比b操作(會導致一部分時間推後)的代價還小,那就不用b了,否則的話我們要在這兩種操作中選擇代價最小的。

還有一個事情……讓我debug到頭禿……咋子處理等待的時間,以及每次需要修改的操作有多少?原來用了特別特別復雜的方法,可是總是會有一點點差錯難以debug出來……最後還是用了rabbithu的方法。每次枚舉一個時間,如果這個時間小於當前枚舉的時間,那就繼續枚舉(就相當於一個upperbound-1啊&……可是我為什麽錯……),之後直接按照題目描述計算就行了……

說到這裏感覺心累……上代碼吧……

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm> 
#include<cmath>
#include<queue>
#define rep(i,a,n) for(ll i = a;i <= n;i++)
#define per(i,n,a) for(ll i = n;i >= a;i--)
#define enter putchar(‘\n‘)

using namespace std;
const int M = 150000;
typedef unsigned long long ll;
ll read()
{
    ll ans = 0,op = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
        if(ch == -) op = -1;
        ch = getchar();
    }
    while(ch >= 0 && ch <= 9)
    {
        ans *= 10;
        ans += ch - 0;
        ch = getchar();
    }
    return ans * op;
}
ll a,b1,c,n,m,t[M],b[M],sumt[M],sumb[M],pt,pb;
ll k1,k2,k3,k4,tot,maxt,ans = 999999999999999999;
void time()
{
    rep(i,1,n) t[i] = read();sort(t+1,t+1+n);
    rep(i,1,n) sumt[i] = sumt[i-1] + t[i];
}
void teach()
{
    rep(i,1,m) b[i] = read();sort(b+1,b+1+m);
    rep(i,1,m) sumb[i] = sumb[i-1] + b[i];    
}
int main()
{
    a = read(),b1 = read(),c = read();
    n = read(),m = read();
    time();teach();
    pt = 0,pb = 0;
    rep(i,1,b[m])
    {
        while(pt < n && t[pt+1] < i) pt++;
        while(pb < m && b[pb+1] < i) pb++;
        k1 = pb * i - sumb[pb],k2 = sumb[m] - sumb[pb] - (m-pb) * i;
        tot = min(k2 * b1, min(k1,k2) * a + (k2 - min(k1,k2)) * b1);
        tot += (pt * i - sumt[pt]) * c;
        ans = min(tot,ans);
    }
    printf("%lld\n",ans);
    return 0;
}

紀念神九發射作業(2)