1. 程式人生 > >[NOIP2018模擬11.02]

[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