1. 程式人生 > 其它 >NOIP-DAY-6總結

NOIP-DAY-6總結

T1-Dove 打撲克

題目描述

Dove和Cicada是好朋友,他們經常在一起打撲克來消遣時光,但是他們打的撲克有不同的玩法。
最開始時,牌桌上有n個牌堆,每個牌堆有且僅有一張牌,第i個牌堆裡那個撲克牌的編號為i,任意兩張牌僅有標號不同。遊戲會進行m輪,每輪Dove可以執行下列操作之一:
·1 x y,將編號為x,y的牌所在的牌堆合併,如果此時x,y已在同一牌堆中,那麼不進行任何操作。
·2 c,詢問有多少對牌堆的牌數之差不少於c。形式化的,對於當前的r個牌堆中,有多少對i,j(i<j),滿足|sizei-sizej|≥c,其中sizei表示第i個牌堆的牌數。
每次Cicada都不能很快的回答出Dove的詢問,為了不讓Cicada難堪,Dove想要寫一個小程式來幫助Cicada,但是Dove還要學高考,所以這個任務就交給你啦!

輸入

第一行兩個空格隔開的整數n,m。
接下來m行,每行1 x y或者2 c,具體含義如上文所示。 (n,c≤105,m≤3×105)

輸出

對於每個詢問,輸出一行一個整數表示答案。 solution: 我們考慮這樣⼀一件事情,形式化的來講就是給定 個⾮非負整陣列成的可重集合,滿⾜Σi=1,n xi=m;
,那麼⼀一定有diff{xn}<=√m,其中代表這個集合中本質不不同的數字的數量量,稍加
思考就能發現這個是正確的,不再加以證明。
知道了了這個結論,做這個題就很容易了。我們考慮維護當前所有存在的集合的⼈數,以及這個⼈數所
對應的集合的數量的一個有序 vector。我們知道當前的不同⼈數的組的數量是不不超過的√m,考慮每
次兩組的合併實際上是對應了⼀個vector 的定點插⼊與刪除,因為對於每次詢問我們還要遍歷⼀
遍,所以暴⼒插入與刪除即可,單次修改複雜度。
再考慮每次詢問,因為我們維護的是有序的 vector,同時任意兩組只有⼈數是有意義的。所以我們雙
指標掃⼀一遍就⾏行行了了,單次回答複雜度,具體細節參考見 std。
總複雜度為O(q√n) ,資料沒有刻意構造,如果有⼀些寫了奇怪演算法的選⼿手⼤概率也可以跑過去。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,m;
ll ans[2][maxn];
int t,s[maxn];
int size[maxn],bl[maxn];
int f[maxn],num[maxn];//表示size為x的集合有多少個
void change(int x,int w)
{
ans[0][x]+=w;//記錄當差值為x的數對個數
ans[1][bl[x]]+=w;
}
int find(int x)
{
if(x==f[x])return x;
return f[x]=find(f[x]);
}
vector<int> vec;
void del(int x)
{
if(x<=t)num[x]--;
else vec.erase(lower_bound(vec.begin(),vec.end(),x));
for(int i=1;i<=t;i++)change(abs(x-i),-num[i]);
for(auto i : vec){change(abs(x-i),-1);}
}
void add(int x)
{
for(int i=1;i<=t;i++)change(abs(size[x]-i),num[i]);//現在size[x]與i的值
for(auto i : vec)change(abs((size[x]-i)),1);
if(size[x]<=t)num[size[x]]++;
else vec.insert(lower_bound(vec.begin(),vec.end(),size[x]),size[x]);
}
ll ask(int x)
{
ll tmp=0;
for(int i=x;i<=bl[x]*t;i++)tmp+=ans[0][i];
for(int i=bl[x]+1;i<=bl[n];i++){tmp+=ans[1][i];}
return tmp;
}
int main()
{
//freopen("cards.in","r",stdin);
//freopen("cards.out","w",stdout);
scanf("%d %d",&n,&m);
t=sqrt(n);
for(int i=1;i<=n;i++)
{f[i]=i,size[i]=1,bl[i]=(i-1)/t+1;}
num[1]=n;
ans[0][0]=ans[1][0]=1ll*n*(n-1)/2;//初始情況的數對個數
for(int i=1;i<=m;i++)
{
int flag,x,y,c;
scanf("%d",&flag);
if(flag==1)
{
scanf("%d %d",&x,&y);
x=find(x);
y=find(y);
if(x==y)continue;
del(size[x]);
del(size[y]);
size[x]+=size[y];
f[y]=x;
add(x);
}
else if(flag==2)

{
scanf("%d",&c);
printf("%lld",ask(max(c,0)));//尋找當前c的值
}
}
return 0;
}
T2-Cicada與排列 Dove 和 Cicada 是好朋友。
高中的⽣活⼤大多是⽆趣的,為了讓自⼰感受到理性愉悅,Dove 和 Cicada 經常在⼀起研究演算法問
題。Cicada 非常菜,經常要向 Dove 請教問題。⼀一天,Cicada 在學習「歸併排序」,並看到了這樣
的程式碼( pascal 版本的程式碼參考見下發⽂檔案)。
inline bool leq(int a, int b) {
return a == b ? rnd() : a < b; // 請注意這⾥裡里的 rnd() 函式!
}
int _a[MAXN];
void merge_sort(int *a, int le, int ri){
if (le == ri) return;
int mi = (le + ri) >> 1;
merge_sort(a, le, mi);
merge_sort(a, mi + 1, ri);
int pp = le, qq = mi + 1;
for (int i = le; i <= ri; i++) {
if (pp == mi + 1) _a[i] = a[qq++];
else if (qq == ri + 1) _a[i] = a[pp++];
else _a[i] = leq(a[pp], a[qq]) ? a[pp++] : a[qq++];
}
memcpy(a + le, _a + le, sizeof(int) * (ri - le + 1));
}

Cicada 感到⾮非常的困惑,為什什麼要專門實現⼀一個⽐比較函式呢?他向 Dove 請教了這個問題,但是
Dove 並不願意和 Cicada 討論這麼無趣的問題。他提出了了⼀個更有意思的問題,假如上⾯的程式碼中
函式 bool rnd() 返回的值是隨機的 true 和 false ,那麼對於⼀個輸⼊的 a[] 來說,每個位
置上的數在執⾏merge_sort(a, 1, N) 後,這個數期望會位於哪個位置呢。
舉個例⼦,⽐如對於數列【3,2,1,2】 來說,最後的新數列⼀定是 【1,2,2,3】,那麼對於3和1來
說,其對應的期望位置⼀定為1和4 ,但是對於第⼆個位置和第四個位置上的數來說,會在
merge_sort(a, 1, 4) 中被合併,第⼆個位置上的數和第四個位置上的數哪個在前⾯是等概率
的,所以對於這個數列來說,最後的期望位置為 【4,1.5,1,2.5】。
愚蠢的 Cicada 並不會解決這個問題,所以就⼜交給你啦!同時注意,為了了避免精度誤差,你需
要輸出答案在mod 998244353下的結果。

solution:

首先糾正一個思路bug,在歸併排序中相同的值一定在一個連續的區間內,但是這並不意味著裡面的任意一個值都可以到裡面任意的一個位置上面去,所以同一個值期望是可能會有區別的的.

概率dp,1.發現數與數之間是沒有太大的關係的,所以我們對於每個數進行單獨的處理(但是在歸併排序的過程中是需要這些看上去沒有什麼用的數進行佔位操作)。2.考慮到歸併排序具有和線段樹相似的結構,(重點在於位置轉化)所以我們可以設dp式子為dp[x][i][j]表示在第x層上原來這個數在i位上後來經過歸併在j位上的期望概率,但是我們發現現在並沒有辦法進行轉移,但是我們發現歸併排序的位置的改變來源於子區間的歸併,所以我們設g[i][]j表示歸併i,j這兩個位置的概率的大小,然後發現可以開始dp了最後列舉每一個數可能會出現的位置進行統計得出答案,

以下是g,dp的轉移。

 1     for(int i=0;i<=mid-l+1;i++)
 2     {
 3         for(int j=0;j<=r-mid;j++)
 4         {
 5             if(i==mid-l+1)g[i][j+1]=(g[i][j+1]+g[i][j])%mod;
 6             else if(j==r-mid)g[i+1][j]=(g[i+1][j]+g[i][j])%mod;
 7             else if(a[l+i]<a[j+mid+1])g[i+1][j]=(g[i+1][j]+g[i][j])%mod;
 8             else if(a[l+i]>a[j+mid+1])g[i][j+1]=(g[i][j+1]+g[i][j])%mod;
 9             else
10             {
11                 g[i+1][j]=(g[i+1][j]+(g[i][j]*modd%mod))%mod;
12                 g[i][j+1]=(g[i][j+1]+(g[i][j]*modd%mod))%mod;
13             } 
14             //cout<<g[i][j]<<endl;
15         }
16     }
17     for(int i=l;i<=r;i++)
18         for(int j=0;j<=mid-l+1;j++)
19             for(int k=0;k<=r-mid;k++)
20             {
21                 if(j==mid-l+1&&k==r-mid)continue;
22                 if(j==mid-l+1)dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][k+mid+1]*g[j][k]%mod)%mod;
23                 else if(k==r-mid)dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][j+l]*g[j][k]%mod)%mod;
24                 else if(a[j+l]<a[k+mid+1])dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][j+l]*g[j][k]%mod)%mod;
25                 else if(a[j+l]>a[k+mid+1])dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][k+mid+1]*g[j][k]%mod)%mod;
26                 else
27                 {
28                     dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][k+mid+1]*g[j][k]%mod*modd%mod)%mod;
29                     dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][j+l]*g[j][k]%mod*modd%mod)%mod;
30                 }
31                 cout<<dp[x][i][j+k+l]<<endl;
32             }

AC程式碼:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=505;
const int mod=998244353;
const int modd=499122177;
int a[maxn],n;
ll dp[maxn][maxn][maxn];
ll g[maxn][maxn];
ll ans=0;
void merge_sort(int x,int l,int r)
{
if(l==r){dp[x][l][l]=1;return;}
int mid=(l+r)>>1;
merge_sort(x+1,l,mid);
merge_sort(x+1,mid+1,r);
memset(g,0,sizeof(g));
g[0][0]=1;//
for(int i=0;i<=mid-l+1;i++)
{
for(int j=0;j<=r-mid;j++)
{
if(i==mid-l+1)g[i][j+1]=(g[i][j+1]+g[i][j])%mod;
else if(j==r-mid)g[i+1][j]=(g[i+1][j]+g[i][j])%mod;
else if(a[l+i]<a[j+mid+1])g[i+1][j]=(g[i+1][j]+g[i][j])%mod;
else if(a[l+i]>a[j+mid+1])g[i][j+1]=(g[i][j+1]+g[i][j])%mod;
else
{
g[i+1][j]=(g[i+1][j]+(g[i][j]*modd%mod))%mod;
g[i][j+1]=(g[i][j+1]+(g[i][j]*modd%mod))%mod;
}
//cout<<g[i][j]<<endl;
}
}
for(int i=l;i<=r;i++)
for(int j=0;j<=mid-l+1;j++)
for(int k=0;k<=r-mid;k++)
{
if(j==mid-l+1&&k==r-mid)continue;
if(j==mid-l+1)dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][k+mid+1]*g[j][k]%mod)%mod;
else if(k==r-mid)dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][j+l]*g[j][k]%mod)%mod;
else if(a[j+l]<a[k+mid+1])dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][j+l]*g[j][k]%mod)%mod;
else if(a[j+l]>a[k+mid+1])dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][k+mid+1]*g[j][k]%mod)%mod;
else
{
dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][k+mid+1]*g[j][k]%mod*modd%mod)%mod;
dp[x][i][j+k+l]=(dp[x][i][j+k+l]+dp[x+1][i][j+l]*g[j][k]%mod*modd%mod)%mod;
}
cout<<dp[x][i][j+k+l]<<endl;
}
sort(a+l,a+r+1);
return;
}
int main()
{
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
merge_sort(1,1,n);
for(int i=1;i<=n;i++)
{
ans=0;
for(int j=1;j<=n;j++)
{
ans=(ans+dp[1][i][j]*j)%mod;
}
printf("%lld ",ans);
}
return 0;
}

T3-Cicada 拿衣服

40~44pt做法solution:

列舉左端點,並且右端點從長度為零開始向右擴充套件並且不斷更新答案,以當前左右端點找到答案後將其維護在一個類線段樹的結構當中記為tag[i]記錄以當前左右端點的最大答案

最後prinf操作也可以由線段樹操作維護,最後的答案輸出在l==r的位置上,並且大區間能保證覆蓋當前的節點保證這件衣服入選答案區間內,以上所有問題被解決。有更多問題可以看看程式碼

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=1e6+5;
int tag[maxn];
int a[maxn];
int mx,mi,orr,ad,val;
int L,R;
void modify(int rt,int l,int r)
{
if(L<=l&&R>=r)
{
tag[rt]=max(tag[rt],val);//當前維護的l到r的區間內的可以得到的長度最大值
return;
}
int mid=(l+r)>>1;
if(L<=mid)modify(rt<<1,l,mid);
if(R>mid)modify(rt<<1|1,mid+1,r);
return;
}
void print(int rt,int l,int r,int now)
{
now=max(now,tag[rt]);//保證包含當前即將得出答案的節點
if(l==r){printf("%d ",now?now:-1);return;}//是否可以得到答案
int mid=(l+r)>>1;
print(rt<<1,l,mid,now);
print(rt<<1|1,mid+1,r,now);
return;
}
int main()
{
freopen("naive.in","r",stdin);
freopen("naive.out","w",stdout);
int n,k;
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int l=1;l<=n;l++)
{
mx=0,mi=inf,ad=(1<<30)-1,orr=0;
for(int r=l;r<=n;r++)
{
mx=max(mx,a[r]);
mi=min(mi,a[r]);//將mi從小端點列舉到更大的右端點保證了正確性
orr=orr|a[r];
ad=ad&a[r];
if(mi+orr-mx-ad>=k)val=r-l+1,R=r;
}
if(R)modify(1,1,n);
}
print(1,1,n,tag[1]);
return 0;
}