洛谷 2 月月賽 II & EE Round 2
A 出言不遜 題目網址
題目描述
珂愛想出公開賽,但每次都被拒絕。
珂愛很生氣,於是學會了出言不遜。
珂愛用一個字符串 S 存儲了她想說的話,但這句話太遜了。爲了出言不遜,珂愛要對字符串進行操作。每次操作,珂愛可以選擇一個字符 c,若 c 在字符串 S 中出現了
x 次,則珂愛會將 x 個字符 c 補到 S 的尾部。
珂愛認爲,這個字符串長度至少爲 L 時,她才能出言不遜。珂愛想要知道,她至少需要操作多少次,才能讓這個字符串的長度大於等於 L。
如果你不告訴珂愛,珂愛會對你出言不遜。
題目思路
純模擬, 把當前出現次數最多的字母添到字符串尾部, 在判斷就可以了。
注意統計完次數後可以用大根堆來取。
注意特判:如果最長的字母都是0的話, 就沒必要繼續了。
代碼
string s;
unsigned long long L;
map<char, unsigned long long> mp;
priority_queue<unsigned long long>pq;
unsigned long long l;
int main()
{
cin >> s;
cin >> L;
l = s.size();
if(s.size() == L-1)
{
cout << 1 << endl;
return 0;
}
for(int i = 0; i < s.size(); i++)
{
mp[s[i]]++;
}
for(int i = '0'; i <= '9'; i++)
{
pq.push(mp[i]);
}
for(int i = 'A'; i <= 'z'; i++)
{
pq.push(mp[i]);
}
int ans = 0;
while(l < L)
{
ans++;
unsigned long long maxn = pq.top();
pq.pop();
if(l + maxn < l) break;
l += maxn;
maxn *= 2;
pq.push(maxn);
}
cout << ans << endl;
return 0;
}
B 諤運算 題目網址
題目描述
首先,CYJian 寫出了一個長度爲 n 的數列 a。
然後他靈光一動,寫出了下面這個諤諤的式子:
CYJian 覺得這個是一個諤運算的簡單式子,摁計算器花了 114514s就算出來了答案。
爲了證明你吊打 114514 個 CYJian,請你在
1s 內算出來這個式子的值吧。你只需要給出答案對 232取模的值即可。
題目思路
暴力肯定是不行的,於是我們選擇先進行二進制拆分, 發現其實每一位對他都有貢獻, 我們把四個數都進行二進制拆分, 發現每一位都有貢獻, 我們可以看到爲1就有貢獻, 四個數式子唯一的情況共有10種。
如下:
0 | 1 | 1 | 0 | |
1 | 0 | 1 | 0 | |
0 1 0 | 0 1 0 | 0 1 0 | 1 | |
1 0 0 | 1 0 0 | 1 0 0 | 1 |
記住每位及它後面的的位都有兩種情況。 所以第p位對答案的貢獻還要乘上2p-1;
設n個數中位數爲0的有x個, 1的有y個
則可得方案數爲
可能還不太懂, 解釋一下:
第一項:三個0, 一個1, 所以字母爲, 參考上面的表格, 一共有兩種情況, 所以係數爲二;
以此類推:
最後乘上貢獻在加起來。
最終答案就爲:
別忘取模QAQ~~
代碼
typedef long long ll;
using namespace std;
ll p = 1;
ll n;
ll a[500010];
ll x[500010], y[500010];
ll mi[50];
ll ans;
void cnt(ll k) // 計算每一位x與y的個數
{
for(register int i=1;i<=33;i++)
{
if(k%2==0) x[i]+=1;
else y[i]+=1;
k/=2;
}
}
ll f(int i) // 求f[i]
{
ll sum=0;
sum=(((2*x[i]*x[i])%p)*((x[i]*y[i])%p))%p;
sum=(sum+(((6*x[i]*x[i])%p)*((y[i]*y[i])%p))%p)%p;
sum=(sum+(((2*x[i]*y[i])%p)*((y[i]*y[i])%p))%p)%p;
return sum;
}
int main()
{
cin >> n;
for(int i = 1; i <= 32; i++)
{
mi[i] = p;
p *= 2;
}
a[33] = p;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
cnt(a[i]);
}
for(int i = 1; i <= 33; i++)
{
if(y[i] && x[i])
ans = (ans + (f(i) * mi[i])) % p;
}
cout << ans << endl;
return 0;
}
C 自然溢出啥事兒沒有 題目網址
題目描述
給定一個整數n, 問有多少種長度爲n的字符串, 滿足這個字符串是一個程序片段。
具體定義如下:
單個分號*;* 是一個語句1
空串* * 是一個程序片段[^2]
如果字符串A是程序片段, 字符串B是語句, 則AB是程序片段。2
如果字符串A是程序片段, 則*{A}是語句塊。3
如果字符串A是語句塊, 則A是語句, []A和A都是函數。4
如果字符串A是函數, 則(A)是函數, A和A()都是值。5
如果字符串A是值, 則(A)是值, A;是語句。6
注意,A是B並不代表B是A*。
題目思路
這題是個dp;
設dp[i][0]是長度爲i的字符串里語句的個數。
dp[i][1]是長度爲i的字符串裏代碼片段的個數。
dp[i][2]是長度爲i的字符串里語句塊的個數。
dp[i][3]是長度爲i的字符串裏函數的個數
dp[i][4]是長度爲i的字符串裏值的個數.
下面我們來逐句地解釋:(見文章末尾)
代碼
unsigned long long n;
unsigned long long dp[100010][5];
int main()
{
cin >> n;
dp[0][1] = dp[1][1] = dp[1][0] = 1;
for(int i = 2; i <= n; i++)
{
dp[i][3] = dp[i-2][3] + dp[i-2][2];
dp[i][2] = dp[i-2][1];
if(i >= 4)
{
dp[i][3] += dp[i-4][2];
}
dp[i][0] = dp[i][2] + dp[i-1][4];
dp[i][4] = dp[i][3] + dp[i-2][4];
for(int j = 0; j < i; j++)
{
dp[i][1] += dp[j][1] * dp[i - j][0];
}
}
cout << dp[n][1] << endl;
return 0;
}
D相同的數字。題目網址
題目描述
每天早上在黑板上會寫有 n 個固定的數字,但是這些數字太無序了,所以每天晚上兔子想把他們變成相同的數字。
有兩種操作 :
1.選擇一個下標k, 兔子把 變成 , 花費的時間爲。
2.選擇一個下標k, 把替換成大於的最小整數, 花費的時間爲
兔子很懶,所以他不想花費太多的時間,你需要幫他計算出將所有數變相同的最小時間。
總共會有 q 天。兔子每天的狀態不同,所以每一天會有不同的 和:。但是黑板上的數不會變。
第一天花費的時間當然會影響第二天的狀態。每天真實的
.
.
其中爲運, 爲上一次的答案, 最初 = 0;
題目思路。
其實我在考場上想出思路來了, 可是由於碼力不行QAQ, 所以沒有模擬出來, 加上對於T3dp鬼題的打擊, 我太菜了~~~~。
其實這個題有很大的切入點, 不想T3, 看起來無從下手。
這個題, 說實話, 真的, 從哪開始想都可以, 我們可以從終點考慮,或者從過程考慮, 假如叢終點, 我們會發現, 最終整個數列變成的數要不是數列中的最大值, 要麼就是大於最大值的最小質數。
再考慮怎麼跳, 顯然要讓花費的時間最小, 肯定要有策略:
首先兩個質數間最大不超過154(因爲第二種變化其實就是跳到下一個質數), 不信可以打表, 上限1e7;
然後設質數的間距爲t, 下面就是用比大小來擇優,由於考慮到後面的優化 我把一種情況的不等式列出來, 就是選c2的:
解得
由於是詢問, 所以對於某次詢問, 最好O(1)就能算出, 所以:
將所有質數跳的次數和跳的距離維護後綴和,然後根據c2/c1的計算找到對應的位置,o(1)即可計算。
所以問題就變爲如何統計這些數在質數上的跳躍次數和+1跳躍次數。這裏的細節比較多。
因爲n,q都比較大,所以應該預處理n,然後再線性處理q。
歐了歐了。。。
附加:最近又新改了一下, 細節都放到代碼裏了。
總之, 臨界值就是c1/c2如果比這個小, 就一步一步跳, 否則就跳質數
代碼
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e7 + 160;
const int mod = (1<<17);
bool isnp[MAXN];
int p[MAXN],pcnt=0;
ll sump[MAXN];//sump[]存儲到當前數爲值出現的質數的個數,其下一個質數就是p[sum[]+1]。
ll n,q,T,a[MAXN],c[2][200],maxa,maxp,ans,lans=0,sumstep[2],behc[2][200],behs[2][200];
//c【i】[j】距離j的次數有幾次
//sumstep是跳一步有幾次
//beh 後綴
void prim(int N) {
isnp[0]=isnp[1]=1;
for(int i=2; i<N; ++i) {
if(!isnp[i]) {
p[++pcnt]=i;
}
sump[i]=pcnt;
for(int j=1; j<=pcnt; ++j) {
if((ll)i*p[j]>=N) break;
isnp[i*p[j]]=1;
if(i%p[j]==0) break;
}
}
}
//求這些數到終點x的的值終點有兩種可能,到n;如果n不是質數,到n對應的下一個質數
void count(int flag,int x){//計算出:1.需要跳多少個1步sumstep;2桶計數c[]:跳到對應的質數間距計數
sumstep[flag]=0;
if(flag==0)//這些數要跳到一個合數,只能+1來跳。
sumstep[flag]+=(n-1)*(a[n]-p[sump[a[n]]]),a[n]=p[sump[a[n]]];
else if(isnp[a[n]]){//可以通過+1跳到這個質數,也可以通過下一個質數跳到。
sumstep[flag]+=p[sump[a[n]]+1]-a[n];
c[flag][sumstep[flag]]++;
a[n]=p[sump[a[n]]+1];
}
int j=sump[a[n]];
for(int i=n;i>1;i--){
sumstep[flag]+=(a[n]-a[i-1]); //+1來挑
while(p[j]>a[i-1]&&p[j-1]>=a[i-1]&&j){
c[flag][p[j]-p[j-1]]+=(i-1);//從質數跳到質數
j--;
}
if(isnp[a[i-1]])c[flag][p[j]-a[i-1]]++; //第一步可能是合數跳到質數
}
//因爲質數跳的距離越大用c2越合算,維護c[t]的後綴和,和所代替步數t*c[t]的後綴和
for(int i=161;i>=1;i--){
behc[flag][i]=behc[flag][i+1]+c[flag][i];
behs[flag][i]=behs[flag][i+1]+i*c[flag][i];
}
a[n]=x;
}
ll work(ll c1,ll c2){
c1=c1^(T*lans),c2=c2^(T*lans);
ll t=ceil(1.0*c2/c1);
if(t>160)t=160;
ll sum0=(sumstep[0]-behs[0][t])*c1+behc[0][t]*c2;
ll sum1=(sumstep[1]-behs[1][t])*c1+behc[1][t]*c2;
return min(sum0,sum1);
}
int main(void) {
// freopen("in.txt","r",stdin);
ll c1,c2;
scanf("%lld%lld%lld",&n,&q,&T);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
sort(a+1,a+1+n);
prim(a[n]+158) ;
count(0,a[n]);
count(1,a[n]);
for(int i=1;i<=q;i++){
scanf("%lld%lld",&c1,&c2);
ans=work(c1,c2);
lans=ans%mod;
printf("%lld\n",ans);
}
}
小結
最近狀態不好, 比賽也沒打好, g估計在家裏憋的老想摸魚, 哎~~
dp[1][0] = 1;
[^2] :dp[0][1] = 1; ↩︎dp[1][1]= 1; dp[i][1] =
解釋, du對於每個字符串, 代碼片段和語句都有不同的長度, 假如代碼片段長度和語句長度都已確定的話, 那麼當前長度的代碼片段個數和語句個數每個都一一對應, 因此要相乘。 ↩︎dp[i][2] = dp[i-2][1]; ↩︎
dp[i][0] = dp[i][2], dp[i-2][2] + dp[i-4][2] = dp[i][3] ↩︎
dp[i][3] +=dp[i-2][3], dp[i][4] = dp[i][3] + dp[i-2][3]; ↩︎
dp[i][4] += dp[i-2][4]; dp[i][0] += dp[i][4];
注意重複d[i][4] += dp[i-2][4]; ↩︎