[NOIP2018模擬11.02]
嗯T1忘記取模了,100到20
嗯T2忘記了那啥定理,暴力也寫炸了,這題我認
嗯T3線段樹合併分裂沒有寫炸,考場上就知道妥妥的70分。但是,分數出的時候聽到有人說暴力也是70分,我???臉黑,枉我敲了一個半小時
據說有大佬的線段樹合併分裂A掉了T3,然而我這份極限資料跑了2.4s的程式碼不敢說話,至今還是黃黃的70分TLE掛在那裡
T1:昆特牌
題目連結:
https://jzoj.net/senior/#contest/show/2546/0
題目:
作為一個資深OIer,你被邀請到位於波蘭的CDPR總部參觀。但沒想到你剛一到就遇到了麻煩。昆特牌的資料庫發生了故障。原本昆特牌中有 k種卡牌和n 種陣營,為了平衡,每個陣營擁有的卡牌種數都是相等的,並且每個陣營的資料順序排列。由於故障,卡牌資料被打亂了,每個陣營現在有ai 種卡牌。因為昆特牌即將迎來重大更新,每種牌的所屬陣營並不重要,工程師只想儘快讓每個陣營擁有相同數量的卡牌。由於資料庫的結構原因,你每單位時間只能將一種牌向左邊或右邊相鄰的一個陣營移動。作為OI選手,這自然是難不倒你,但作為一名卡牌遊戲愛好者,你想知道最終的卡牌分佈有多少種方案。兩種方案不同當且僅當存在一種卡牌,它在兩種方案中所屬陣營不同。對998244353取模
題解:
就是均分紙牌問你方案數。很自然聯想均分紙牌的做法,發現變成負數的時候好像搞不了方案數啊。於是很大膽的猜測只要給出去牌之後自己還是正的,現在給和以後給都是一樣的。那麼我就是要儘可能的讓當前要給牌的位置牌足夠多。什麼時候足夠多呢?所有的該給它的牌都給它就是了。發現就是連個邊拓撲排序一下就ok了,邊權就是需要給的牌的數量
哎,別忘了取模
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
#include<vector>
#include <queue>
using namespace std;
typedef long long ll;
const int N=1e6+15;
const ll mo=998244353;
int n,k;
ll a[N],b[N],fac[N],in[N];
struct node{
int to;
ll w;
};
vector <node> p[N];
inline ll read(){
char ch=getchar();ll s=0,f=1;
while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*f;
}
queue <int> q;
ll qpow(ll a,ll b)
{
ll re=1;
for (;b;b>>=1,a=a*a%mo) if (b&1) re=re*a%mo;
return re;
}
ll C(ll a,ll b)
{
return fac[a]*qpow(fac[b],mo-2)%mo*qpow(fac[a-b],mo-2)%mo;
}
ll topo()
{
ll ans=1;
while (!q.empty()) q.pop();
for (int i=1;i<=n;i++) if (!in[i]) q.push(i);
while (!q.empty())
{
int k=q.front();q.pop();
for (int i=0;i<p[k].size();i++)
{
node u=p[k][i];
ans=ans*C(b[k],u.w)%mo;
b[k]-=u.w;
b[u.to]+=u.w;
in[u.to]--;
if (!in[u.to]) q.push(u.to);
}
}
return ans;
}
int main()
{
freopen("gwent.in","r",stdin);
freopen("gwent.out","w",stdout);
fac[0]=1;
for (int i=1;i<N;i++) fac[i]=fac[i-1]*i%mo;
int T=read();
while (T--)
{
n=read();k=0;
for (int i=1;i<=n;i++)
{
a[i]=read();
b[i]=a[i];
k+=a[i];
p[i].clear();
}
k/=n;
for (int i=1;i<=n;i++)
{
if (a[i]==k) continue;
if (a[i]>k)
{
ll q=a[i]-k;
p[i].push_back((node){i+1,q});
in[i+1]++;
a[i]-=q;
a[i+1]+=q;
}
else
{
ll q=k-a[i];
p[i+1].push_back((node){i,q});
in[i]++;
a[i]+=q;
a[i+1]-=q;
}
}
printf("%lld\n",topo());
}
return 0;
}
View Code
T2:時空幻境
題目連結:
https://jzoj.net/senior/#contest/show/2546/1
題目:
Tim擁有控制時間的能力。他學會了BFS後,出了一道題:求出一張無向圖中連通塊的個數。他想請你做出這道題來
題解:
我們定義每次從x到超過n被取模為一輪,有個結論就是說若是初始的x不同,這一輪中的邊都不同。更深入的就是說,在碰到x相等之前,每連一條邊都會減少一個連通塊
我們定義從開始到回到x為一個迴圈,顯然一旦我們找到最小迴圈節後面就不需要做下去了,可以直接計算答案
首先我們找最小迴圈節,$x \times k^p \,\ \equiv \,\ x (\mod n)$,最小迴圈節為使得上式成立的最小p
根據$x \times k^{\varphi(n)} \,\ \equiv \,\ x \,\ (\mod n)$,我們知道$p|\varphi(n)$
由於n是固定的,我們預處理$\varphi(n)$的約數從小到大列舉快速冪判斷即可
找到最小迴圈節之後呢?
最小迴圈節是偶數就是隔一個連一條邊,直到某個$a_y==x$就停下;是奇數就是一直合併,兩次到某個$a_y==x$才停下,也就是直到成環,但是注意最後一條完成環的邊不能算入答案
畫畫圖對理解有幫助
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int N=25;
int cnt;
ll pri[N];
void div(ll x)
{
for (int i=1;1ll*i*i<=x;i++)
{
if (x%i) continue;
pri[++cnt]=i;if (1ll*i*i!=x) pri[++cnt]=x/i;
}
}
ll qpow(ll a,ll b,ll mod)
{
ll re=1;
for (;b;b>>=1,a=a*a%mod) if (b&1) re=re*a%mod;
return re;
}
int main()
{
freopen("braid.in","r",stdin);
freopen("braid.out","w",stdout);
div(998244352);
sort(pri+1,pri+1+cnt);
int T;
ll n,m,x,k;
scanf("%d",&T);
while (T--)
{
scanf("%lld%lld",&n,&m);
scanf("%lld%lld",&x,&k);
ll ans;
for (int i=1;i<=cnt;i++)
{
if (qpow(k,pri[i],n)==1)
{
ans=pri[i];
break;
}
};
if (ans==1) ans=0;//特判一下
if (ans&1) ans--;//完成了環
else ans/=2;//邊數等於點數除2
if (ans<m) printf("%lld\n",n-ans);
else printf("%lld\n",n-m);
}
return 0;
}
View Code
T3:初音未來
題目連結:
https://jzoj.net/senior/#contest/show/2546/2
題目:
Hercier作為一位喜愛Hatsune Miku的OIer,痛下決心,將Vocaloid買回了家。開啟之後,你發現介面是一個長為n的序列,代表音調,並形成了全排列。你看不懂日語,經過多次嘗試,你只會用一個按鈕:將一段區間按升序排序。不理解音樂的Hercier決定寫一個指令碼,進行m次操作,每次對一段區間進行操作。可惜Hercier不會寫指令碼,他找到了在機房裡的你,請你模擬出最後的結果。
題解:
部分分:經典題目,二分答案後變為區間查詢,區間set1,0.見 [HEOI2016/TJOI2016]排序
一個序列交換相鄰的兩個數進行排序的話,最小次數就是逆序對個數,具體操作方法就是每次交換相鄰逆序對。
所 以將排序過程變為交換相鄰位置直到沒有逆序對。記錄哪些位置是逆序對。
這個過程可以用set維護,每次二分出逆序對的位置,如果再區間內,則交換之,並將兩側出現的新逆序對加入。
由於只會交換$O(n^2)$次,總的時間複雜度為$O((n^2+m)log n)$
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int N=25;
int cnt;
ll pri[N];
void div(ll x)
{
for (int i=1;1ll*i*i<=x;i++)
{
if (x%i) continue;
pri[++cnt]=i;if (1ll*i*i!=x) pri[++cnt]=x/i;
}
}
ll qpow(ll a,ll b,ll mod)
{
ll re=1;
for (;b;b>>=1,a=a*a%mod) if (b&1) re=re*a%mod;
return re;
}
int main()
{
freopen("braid.in","r",stdin);
freopen("braid.out","w",stdout);
div(998244352);
sort(pri+1,pri+1+cnt);
int T;
ll n,m,x,k;
scanf("%d",&T);
while (T--)
{
scanf("%lld%lld",&n,&m);
scanf("%lld%lld",&x,&k);
ll ans;
for (int i=1;i<=cnt;i++)
{
if (qpow(k,pri[i],n)==1)
{
ans=pri[i];
break;
}
};
if (ans==1) ans=0;//特判一下
if (ans&1) ans--;//完成了環
else ans/=2;//邊數等於點數除2
if (ans<m) printf("%lld\n",n-ans);
else printf("%lld\n",n-m);
}
return 0;
}
View Code