10.04 FZSZ模拟Day1 总结

今天轮到FZSZ出题了,这可是连续两年捧杯NOI的学校了……

可想而知今天题难度有多大……不过似乎还要庆幸出题的是一位叫Anzhe Wang 的大神而不是fjzzq?

T1.permutation

期望得分40,实际得分40.

这道题看起来很像是组合题……想起昨天的组合题,想试试能不能用类似的做法去做。后来发现不可行,不可递推,因为昨天的题其实还很良心,只是相邻两个元素之间会互相影响,而这个题前面的元素会影响到后面的元素,所以难以递推。

我大概开场想了30min没思路 然后就去看后面了……

回来大概一想想到这题40pts可以用状压DP水过,就是某一位上是i表示那一位当前是被选取状态,这样其实我们是可以通过转移来确定先后顺序的。这样复杂度是O(2^n * n)的,可以过40pts。

然后看了题解……题解真是让人脑洞大开……其实我们发现这个选取的情况可以对应一个二分图。我们把所有的pi放在一个点集,所有的i(也可以理解为位置)放在一个点集。

如果我们令二分图中的连边表示实际匹配时的不合法情况,我们会得到以下的图。

 

也就是说,这个二分图是由许多条不相交的链组成的(所谓的不相交是指没有属于不同链的两条边连在一个点上),那么我们要求的其实就是这个二分图的补图的匹配方案数。(补图简单的定义就是,原二分图中有的边它没有,原来没有的边他有。)

我们令g[i]表示在这个二分图中选取i条边的方案数(也就是等于确定了i个不合法的情况)

那么我们得到答案有如下式子:

 

 来解释一下,首先(n-i)!表示一个全排列,就是因为你当前确定有i条边是不合法的,那么剩余的n-i条边就是自由排列的,那就是它的阶乘次的方案数。后面的是什么意思呢?首先我们知道如果啥都不管的话,那么n!是所有的情况,但是你在n!种的情况之中,必然会统计到有一条边是不合法的情况,于是乎我们就要去计算有一条边不合法的情况,然后把它减掉。但是在这样计算的时候又会多把有两条边不合法的情况减掉,相当于多减了,然后我们还得给加回来……所以这样层层递推就有了最后的式子。

然后我们去求g[i],用f[i][j][0/1]表示在一条链上取到第i个点,取了j条链,当前点取或者没取的情况数,那么就有转移:

f[i][j][0] = f[i-1][j][1] + f[i-1][j][0];

f[i][j][1] = f[i-1][j-1][0];

最后我们直接用f把g合并出来就可以了。时间复杂度是O(n^2/k + n)?能过95.最后一个点什么NTT真的不会……

然而这个也不想写……

40pts状压代码:

 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<utility>
#include<map>
#define pr pair<int,int>
#define mp make_pair
#define fi first
#define sc second
#define lowbit(x) x & (-x)
#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;
typedef long long ll;
const int M = 2000005;
const int N = 10000005;
const ll mod = 998244353;

int read()
{
    int 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;
}

int n,k,cur;
ll dp[M];

int getsum(int x)
{
    int s = 0;
    while(x) s++,x -= lowbit(x);
    return s;
}

int main()
{
    freopen("permutation.in","r",stdin);
    freopen("permutation.out","w",stdout);
    n = read(),k = read();
    rep(i,0,n-1) if(i != k) dp[1<<i] = 1;
    rep(i,1,(1<<n)-1)
    {
        int h = getsum(i);
        rep(j,0,n-1)
        {
            if(i & (1 << j) || (abs(j-h) == k)) continue;
            dp[i | (1 << j)] += dp[i],dp[i | (1 << j)] %= mod;
        }
    }
    printf("%lld\n",dp[(1<<n)-1]);
    return 0;
}

 

 

 

看一下学姐的代码。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <ctime>
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define enter putchar('\n')
#define space putchar(' ') 
//#define ivorysi
#define MAXN 100005
typedef long long int64;
using namespace std;
template<class T>
void read(T &res) {
    res = 0;char c = getchar();T f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = getchar();
    }
    res *= f;
}
template<class T> 
void out(T x) {
    if(x < 0) {x = -x;putchar('-');}
    if(x >= 10) {
        out(x / 10);
    }
    putchar('0' + x % 10);
}
const int MOD = 998244353;
int inc(int a,int b) {
    return a + b >= MOD ? a + b - MOD : a + b;
}
int mul(int a,int b) {
    return 1LL * a * b % MOD;
}
void update(int &x,int y) {
    x = inc(x,y);
}
int fac[MAXN],N,K,g[MAXN],f[2005 * 2][2005][2];
bool vis[2][MAXN];
void DP(int st,int cnt) {
    for(int j = 0 ; j <= N ; ++j) update(f[st + 1][j][0],inc(f[st][j][0],f[st][j][1]));
    for(int h = st + 2 ; h <= st + cnt ; ++h) {
        for(int j = 0 ; j <= N ; ++j) {
            update(f[h][j][0],inc(f[h - 1][j][1],f[h - 1][j][0]));
            if(j >= 1) update(f[h][j][1],f[h - 1][j - 1][0]);
        }
    }
}
void Solve() {
    read(N);read(K);
    fac[0] = 1;
    for(int i = 1 ; i <= N ; ++i) fac[i] = mul(fac[i - 1],i);
    int tot = 0;
    memset(vis,0,sizeof(vis));
    f[0][0][0] = 1;
    for(int i = 1 ; i <= N ; ++i) {
        if(!vis[0][i]) {
            int cnt = 0;
            for(int j = i ; j <= N ; j += 2 * K) {
                vis[0][j] = 1;
                cnt++;
                if(j + K <= N) {vis[1][j + K] = 1;++cnt;}
            }
            DP(tot,cnt);
            tot += cnt;
        }
        if(!vis[1][i]) {
            int cnt = 0;
            for(int j = i ; j <= N ; j += 2 * K) {
                vis[1][j] = 1;
                ++cnt;
                if(j + K <= N) {
                    vis[0][j + K] = 1;++cnt;
                }
            }
            DP(tot,cnt);
            tot += cnt;
        }
    }
    for(int i = 0 ; i <= N ; ++i) {
        g[i] = inc(f[2 * N][i][0],f[2 * N][i][1]);
    }
    int t = 1,ans = 0;
    for(int i = 0 ; i <= N ; ++i) {
        update(ans,mul(t,mul(g[i],fac[N - i])));
        t = mul(t,MOD - 1);
    }
    out(ans);enter;
}
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#else
    freopen("permutation.in","r",stdin);
    freopen("permutation.out","w",stdout);
#endif
    Solve();
    return 0;
}

T2.tree

期望得分30,实际得分40

这题大概看了20多分钟,没什么思路,但是可以疯狂爆搜,暴力枚举哪两条边要删除,然后暴力dfs判断图是否联通,复杂度O(n^3),得分40.

这道题的60pts做法是枚举每一条原来的树边,之后对新图跑他让tarjan去求桥,每座非树边的桥有1的贡献。

然后满分的做法就是,我们发现对于一条树边,一棵子树内的所有节点如果有两条或者以上连了出去,那么你怎么割也无法割断,如果只有一条,那么就有1的贡献,如果没有,那就随便找一条割断,也就是有m的贡献。

所以我们可以进行树上差分。对于每一条新加的边,我们把它拆成从一个点到LCA和另一个点到LCA的两条路径,分别差分维护。最后我们统计一下,每个点的点权为1则有1的贡献,为0有m的贡献。

我写的时候是用树剖的,也能过300000.

然后学姐还有更强的操作,直接维护dfs序,统计一个子树管辖的区间之内能向左/右延伸的最远的两条边能延伸到的范围,最后统计的时候如果有两天或以上的边,你就割不断,有一条贡献为1,0条则贡献为m。(%学姐orz)

40pts爆搜不看了,直接上100的树剖。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<utility>
#include<map>
#define pr pair<int,int>
#define mp make_pair
#define fi first
#define sc second
#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;
typedef long long ll;
const int M = 300005;
const int N = 10000005;

int read()
{
    int 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;
}

struct edge
{
    int next,to;
}e[M<<2];

struct seg
{
    int v,lazy;
}t[M<<2];

int n,m,dfn[M],head[M],ecnt,size[M],fa[M],dep[M],top[M],hson[M],idx,ch[M],x,y;
ll ans;

void add(int x,int y)
{
    e[++ecnt].to = y;
    e[ecnt].next = head[x];
    head[x] = ecnt;
}

void dfs1(int x,int f,int depth)
{
    fa[x] = f,dep[x] = depth,size[x] = 1;
    int maxson = -1;
    for(int i = head[x];i;i = e[i].next)
    {
        if(e[i].to == f) continue;
        dfs1(e[i].to,x,depth+1);
        size[x] += size[e[i].to];
        if(size[e[i].to] > maxson) maxson = size[e[i].to],hson[x] = e[i].to;
    }
}

void dfs2(int x,int t)
{
    top[x] = t,dfn[x] = ++idx;
    if(!hson[x]) return;
    dfs2(hson[x],t);
    for(int i = head[x];i;i = e[i].next)
    {
        if(e[i].to == fa[x] || e[i].to == hson[x]) continue;
        dfs2(e[i].to,e[i].to);
    }
}

void pushdown(int p,int l,int r)
{
    int mid = (l+r) >> 1;
    t[p<<1].lazy += t[p].lazy,t[p<<1|1].lazy += t[p].lazy;
    t[p<<1].v += t[p].lazy * (mid-l+1),t[p<<1|1].v += t[p].lazy * (r-mid);
    t[p].lazy = 0;
}

void modify(int p,int l,int r,int kl,int kr)
{
    if(l == kl && r == kr) 
    {
        t[p].v += (r-l+1),t[p].lazy++;
        return;
    }
    int mid = (l+r) >> 1;
    if(t[p].lazy) pushdown(p,l,r);
    if(kr <= mid) modify(p<<1,l,mid,kl,kr);
    else if(kl > mid) modify(p<<1|1,mid+1,r,kl,kr);
    else modify(p<<1,l,mid,kl,mid),modify(p<<1|1,mid+1,r,mid+1,kr);
}

int query(int p,int l,int r,int pos)
{
    if(l == r) return t[p].v;
    int mid = (l+r) >> 1;
    if(t[p].lazy) pushdown(p,l,r);
    if(pos <= mid) return query(p<<1,l,mid,pos);
    else return query(p<<1|1,mid+1,r,pos);
}

void mrange(int x,int y)
{
    while(top[x] != top[y])
    {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        modify(1,1,n,dfn[top[x]],dfn[x]);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    if(dfn[x] + 1 > dfn[y]) return;
    modify(1,1,n,dfn[x]+1,dfn[y]);
}

int main()
{
//    freopen("tree.in","r",stdin);
//    freopen("tree.out","w",stdout);
    n = read(),m = read();
    rep(i,1,n-1) x = read(),y = read(),add(x,y),add(y,x);
    dfs1(1,0,1),dfs2(1,1);
    rep(i,1,m) x = read(),y = read(),mrange(x,y);
    rep(i,1,n) ans += (query(1,1,n,dfn[i]) == 1);
    rep(i,2,n) ans += (query(1,1,n,dfn[i]) == 0) * m;
    printf("%lld\n",ans);
    return 0;
}

T3.polynomial

期望得分30,实际得分0.

这道题是真心不可做……题解里面什么FFT是搞哪样……

考试的时候暴力打表n<=4的情况,结果发现自己推4个一样的时候推错了,多推了3个,爆零。

还是放上改好的暴力打表30吧……正解代码11kb又是哪样……

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<utility>
#include<map>
#define pr pair<int,int>
#define mp make_pair
#define fi first
#define sc second
#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;
typedef long long ll;
const int M = 100005;
const int N = 10000005;

int read()
{
    int 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;
}

struct card
{
    int num,col;
}c[50];

int T,n,posa,posb,cur;
char s[5];
bool jok;

void clear()
{
    memset(c,0,sizeof(c));
    jok = 0,cur = posa = posb = 0;
}

bool same()
{
    return (c[1].num == c[2].num) && (c[2].num == c[3].num) && (c[3].num == c[4].num);
}

bool ssame()
{
    rep(i,1,n)
    {
        rep(j,i+1,n)
        rep(k,j+1,n)
        if((c[i].num == c[j].num) && (c[j].num == c[k].num)) return 1;
    }
    return 0;
}

bool tsame()
{
    rep(i,1,n)
    {
        rep(j,i+1,n)
        if(c[i].num == c[j].num) 
        {
            posa = i,posb = j;
            return 1;
        }
    }
    return 0;
}

void naive()
{
    if(n == 1) 
    {
        printf("1\n");
        return;
    }
    if(n == 2)
    {
        if(c[1].num == c[2].num) printf("2\n");
        else if(c[1].num + c[2].num == 29) printf("2\n");
        else printf("1\n");
        return;
    }
    if(n == 3)
    {
        if(c[1].num == c[2].num && c[2].num == c[3].num) printf("5\n");
        else 
        {
            rep(i,1,n)
            rep(j,i+1,n) 
            {
                if(c[i].num == c[j].num || c[i].num + c[j].num == 29)
                {
                    printf("2\n");
                    return;    
                }
            }
            printf("1\n");
            return;
        }
    }
    rep(i,1,n) 
    {
        rep(j,i+1,n) 
        if(c[i].num + c[j].num == 29) jok = 1;
    }
    if(jok)
    {
        rep(i,1,n)
        rep(j,i+1,n) 
        {
            if(c[i].num == c[j].num)
            {
                printf("4\n");
                return;
            }
        }
        printf("2\n");
        return;
    }
    if(same())
    {
        printf("15\n"); 
        return;
    }
    if(ssame())
    {
        printf("6\n");
        return ;
    }
    if(tsame())
    {
        cur = 0;
        rep(i,1,n)
        {
            if(i == posa || i == posb) continue;
            if(cur == c[i].num) 
            {
                printf("4\n");
                return;
            }
            if(!cur) cur = c[i].num;
        }
        printf("2\n");
        return;
    }
    printf("1\n");
    return;
}

int main()
{
    freopen("polynomial.in","r",stdin);
    freopen("polynomial.out","w",stdout);
    srand(19260817);
    T = read();
    while(T--)
    {
        clear();
        n = read();
        rep(i,1,n) 
        {
            scanf("%s",s);
            if(s[0] >= '2' && s[0] <= '9') c[i].num = s[0] - '0';
            else if(s[0] < '0' || s[0] > '9') c[i].num = s[0] - 'A' + 1;
            else c[i].num = 10 + s[1] - '0';
        }
        rep(i,1,n) c[i].col = read();
        if(n <= 4) naive();
        else printf("%d\n",rand());
    }
    return 0;
}

感觉自己还是太弱,不知道明天能咋样orz。

转载于:https://www.cnblogs.com/captain1/p/9743367.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值