noip2021训练5


Link

SDOI2010 地精部落

法一:

首先有两个性质

  • 在一个波动序列中,若 \(i\)\(j\) 不相邻,那么交换 \(i,j\) 后依然是一个波动序列。
  • 把波动序列中的 \(a_i\) 都改为 \(n+1-a_i\),还是一个波动序列,且山峰与山谷相反。

\(f_{i,j}\) 表示选 \([1,i]\) 这些数,第一个为山峰且为 \(j\) 的方案数(山谷与山峰的方案数是一样的)

转移 \(f_{i,j}=f_{i,j-1}+f_{i-1,i-j+1}\)

\(j\)\(j-1\) 不相邻(\(j\) 表示的是数而不是位置),交换后仍是合法的,所以是 \(f_{i,j-1}\)

\(j\)\(j-1\) 相邻,因为第一个数 \(j\) 为山峰,所以 \(j-1\) 必为山谷,此时确定了第一个为 \(j\),所以一共选了 \(i-1\) 个数,根据性质二\(j-1\) 为山谷的方案数数与 \((i-1)+1-(j-1)\) 为山峰的方案数是相同的,所以是 \(f_{i-1,i-j+1}\)

最后答案为 \(\sum_{i=1}^nf_{n,i}\)

转移时可以用滚动数组优化一下空间

法二:

\(f_{i,0/1}\) 分别表示长度为 \(i\) 且第一位为山峰/山谷的方案数。

转移时可以按最大的数分成两半,然后将两边的方案数乘起来,再乘上一个组合数。

因为最大的数必定是山峰,与它相邻的必定是山谷,所以根据其位置 \(j\) 的奇偶性,可以得到前面一段的第一位为山峰还是山谷,同理可得后面一段的。

然后就可以转移了。

其中组合数为在 \(i-1\) 个数中选 \(j\) 个放在前面,也就是 \(C_{i-1}^j\)

Code
#include 
#include 
#define ll long long

using namespace std;

const int N = 4210;
int n, p;
int f[N][2], C[N][N];

int main()
{
    ios :: sync_with_stdio(false);
    cin >> n >> p;
    f[0][0] = f[0][1] = 1;
    f[1][0] = f[1][1] = 1;
    for(int i = 0; i <= n; i++)
    {
        C[i][0] = 1;
        for(int j = 1; j <= i; j++)
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % p;
    }
    for(int i = 2; i <= n; i++)
    {
        for(int j = 0; j < i; j++)
        {
            if(j & 1) f[i][1] = (f[i][1] + (ll)C[i - 1][j] * f[j][1] % p * f[i - 1 - j][0] % p) % p;
            else f[i][0] = (f[i][0] + (ll)C[i - 1][j] * f[j][0] % p * f[i - 1 - j][0] % p) % p;
        }
    }
    cout << (f[n][0] + f[n][1]) % p << endl;
    return 0;
}
// A.S.

HAOI2010 软件安装

首先不难想到缩点,因为环内的点必须全选才能产生贡献

由于一个软件最多依赖另外一个软件,所以得到了一棵树,然后就是树上背包板子了

要保证选了父亲才能选其儿子

我写的 \(O(n^3)\)

不过这种题好像可以处理出 dfs 序,然后就可以 \(O(n^2)\) dp 了?

Code
#include 
#define pb push_back

using namespace std;

namespace IO
{
    template 
    inline void read(T &x)
    {
        x = 0; int f = 1; char c = getchar();
        while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
        while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
        x *= f;
        return;
    }

    template 
    inline void write(T x)
    {
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 110;
const int M = 510;

int n, m, a[N], b[N], fa[N];
vector  g[N];
vector  G[N];
int dfn[N], low[N], tim;
bool vis[N];
int stk[N], top, cnt, id[N];
int val[N], deg[N], w[N], v[N];

void tarjan(int u)
{
    dfn[u] = low[u] = ++tim;
    stk[++top] = u;
    vis[u] = 1;
    for(auto v : g[u])
    {
        if(!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
        else if(vis[v]) low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        int x; cnt++;
        do {
            x = stk[top--];
            w[cnt] += a[x];
            v[cnt] += b[x];
            id[x] = cnt;
            vis[x] = 0;
        } while(x != u);
    }
    return;
}

int f[N][M];

void dfs(int u)
{
    for(int i = w[u]; i <= m; i++)
        f[u][i] = v[u];
    for(auto v : G[u])
    {
        dfs(v);
        for(int i = m; i >= w[v]; i--)
            for(int j = 0; j <= i - w[u]; j++)
                f[u][i] = max(f[u][i], f[u][i - j] + f[v][j]);
    }
    return;
}

int main()
{
    read(n), read(m);
    for(int i = 1; i <= n; i++) read(a[i]);
    for(int i = 1; i <= n; i++) read(b[i]);
    for(int i = 1; i <= n; i++) read(fa[i]), g[fa[i]].pb(i);

    for(int i = 1; i <= n; i++)
        if(!dfn[i]) tarjan(i);

    for(int i = 1; i <= n; i++)
        for(auto j : g[i])
            if(id[i] != id[j])
                G[id[i]].pb(id[j]), deg[id[j]]++;

    for(int i = 1; i <= cnt; i++)
        if(!deg[i]) G[0].pb(i);

    dfs(0);
    write(f[0][m]), putchar('\n');

    return 0;
}
// A.S.

BJWC2012 冻结

分层图最短路板子

一定要注意边的数量,算不清就开大点!!!

Code
#include 

using namespace std;

namespace IO
{
    template 
    inline void read(T &x)
    {
        x = 0; int f = 1; char c = getchar();
        while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
        while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
        x *= f;
        return;
    }

    template 
    inline void write(T x)
    {
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 2600;
const int M = 2e5;

int n, m, k;
struct edge
{
    int v, w, nxt;
} e[M];
int head[N], tot;

void link(int u, int v, int w)
{
    e[++tot] = (edge) {v, w, head[u]};
    head[u] = tot;
}

queue  que;
int dis[N];
bool vis[N];

void spfa()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    que.push(1), vis[1] = 1;
    while(!que.empty())
    {
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].v;
            if(dis[v] > dis[u] + e[i].w)
            {
                dis[v] = dis[u] + e[i].w;
                if(!vis[v]) que.push(v), vis[v] = 1;
            }
        }
    }
    return;
}

int main()
{
    read(n), read(m), read(k);
    for(int i = 1, u, v, w; i <= m; i++)
    {
        read(u), read(v), read(w);
        for(int j = 0; j <= k + 1; j++)
        {
            int x = u + j * n, y = v + j * n;
            link(x, y, w), link(y, x, w);
            link(x, y + n, w >> 1), link(y, x + n, w >> 1);
        }
    }

    spfa();

    int ans = 1e9;
    for(int i = 1; i <= k + 1; i++)
        ans = min(ans, dis[i * n]);
    write(ans), putchar('\n');

    return 0;
}
// A.S.

SDOI2012 Longge 的问题

经典数学题

\(\sum_{i=1}^n\gcd(i,n)\)

考虑对于每个 \(\gcd\) 有几个 \(i\),统计贡献。

\(cnt(x)\) 表示 \([1,n]\) 中与 \(n\) 的最大公因数为 \(x\) 的数的个数

\[cnt(x)=\sum_{i=1}^n[\gcd(i,n)=1]=\sum_{i=1}^{\left\lfloor \frac{n}{x}\right\rfloor}[\gcd(i,\left\lfloor \frac{n}{x}\right\rfloor)=1] \]

我们发现 \(cnt(x)=\phi(\left\lfloor \frac{n}{x}\right\rfloor)\)

所以原式可以化简为

\[\sum_{i=1}^n=\sum_{d|n}d\sum_{i=1}^n[\gcd(i,n)=d]=\sum_{d|n}d\times\phi(\left\lfloor \frac{n}{d}\right\rfloor) \]

然后 \(O(\sqrt{n})\) 枚举 \(d\)\(O(\sqrt{n})\) 单点求 phi就可以了。

Code
#include 
#define int long long

using namespace std;

int phi(int x)
{
    int res = x;
    for(int i = 2; i * i <= x; i++)
    {
        if(x % i == 0)
        {
            res = res / i * (i - 1);
            while(x % i == 0) x /= i;
        }
    }
    if(x > 1) res = res / x * (x - 1);
    return res;
}

signed main()
{
    int n, ans = 0;
    scanf("%lld", &n);
    for(int i = 1; i * i <= n; i++)
    {
        if(n % i == 0)
        {
            ans += i * phi(n / i);
            if(n / i != i) ans += n / i * phi(i);
        }
    }
    printf("%lld\n", ans);
    return 0;
}
// A.S.

NOI2015 程序自动分析

并查集开两倍,分别表示是否相等

Code
#include
#include
#include
#include

using namespace std;

const int N=1e6+10;
struct node
{
	int x,y,e;
}a[N];
int num[N<<1],cnt;
int f[N];

int Find(int a)
{
	return f[a]==a?a:f[a]=Find(f[a]);
}

bool cmp(node A,node B)
{
	return A.e>B.e;
}

int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n*2;i++) f[i]=i;
		cnt=-1;
		memset(num,0,sizeof(num));
		memset(a,0,sizeof(a));
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].e);
			num[++cnt]=a[i].x;
			num[++cnt]=a[i].y;
		}
		sort(num,num+cnt);
		int tot=unique(num+1,num+1+cnt)-num;
		for(int i=1;i<=n;i++)
		{
			a[i].x=lower_bound(num,num+tot,a[i].x)-num;
			a[i].y=lower_bound(num,num+tot,a[i].y)-num;
		}
		for(int i=1;i<=n;i++) f[i]=i;
		sort(a+1,a+1+n,cmp);
		int flag=0;
		for(int i=1;i<=n;i++)
		{
			int fx=Find(a[i].x),fy=Find(a[i].y);
			if(a[i].e==1) f[fx]=fy;
			else if(fx==fy) {flag=1;break;}
		}
		if(!flag) puts("YES");
		else puts("NO");
	}
	return 0;
}
// A.S.

JSOI2008 最小生成树计数

首先有一个性质:不同的最小生成树中权值相同的边的个数是相同的(比较显然?)

因为 具有相同权值的边不会超过10条,所以可以直接暴力。

先求出最小生成树中每个权值有几条边。

处理出权值相同的边,排序后存一下同一个权值的左右端点,对可以构成生成树的边进行乘法原理即可。

使用搜索,并随时判断是否出现了环。

Code
#include 

using namespace std;

namespace IO
{
    template 
    inline void read(T &x)
    {
        x = 0; int f = 1; char c = getchar();
        while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
        while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
        x *= f;
        return;
    }

    template 
    inline void write(T x)
    {
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 110;
const int M = 1010;
const int p = 31011;

int n, m, cnt;
struct edge
{
    int u, v, w;
    friend bool operator < (edge x, edge y)
    {
        return x.w < y.w;
    }
} e[M];

struct node
{
    int l, r, num;
} a[M];

int f[N];

void init()
{
    for(int i = 1; i <= n; i++) f[i] = i;
}

int Find(int a)
{
    return f[a] == a ? a : Find(f[a]);
}

int sum;
void dfs(int id, int now, int k)
{
    if(now == a[id].r + 1)
    {
        if(k == a[id].num) sum++;
        return;
    }
    int x = Find(e[now].u), y = Find(e[now].v);
    if(x != y)
    {
        f[x] = y;
        dfs(id, now + 1, k + 1);
        f[x] = x, f[y] = y;
    }
    dfs(id, now + 1, k);
}

int main()
{
    read(n), read(m);
    for(int i = 1; i <= m; i++)
        read(e[i].u), read(e[i].v), read(e[i].w);
    sort(e + 1, e + 1 + m);

    init();
    int tot = 0;
    for(int i = 1; i <= m; i++)
    {
        if(e[i].w != e[i - 1].w) cnt++, a[cnt - 1].r = i - 1, a[cnt].l = i;
        int x = Find(e[i].u), y = Find(e[i].v);
        if(x != y) f[x] = y, a[cnt].num++, tot++;
    }
    a[cnt].r = m;
    if(tot != n - 1)
    {
        puts("0");
        return 0;
    }

    init();
    int ans = 1;
    for(int i = 1; i <= cnt; i++)
    {
        sum = 0;
        dfs(i, a[i].l, 0);
        ans = ans * sum % p;
        for(int j = a[i].l; j <= a[i].r; j++)
        {
            int x = Find(e[j].u), y = Find(e[j].v);
            if(x != y) f[x] = y;
        }
    }
    write(ans), putchar('\n');
    return 0;
}
// A.S.

HEOI2014 南园满地堆轻絮

使得最大值最小,肯定要二分答案

考虑如何 chk,贪心的想,因为要使序列不下降,所以要尽量让前面的更小,所以就很好写了。

复杂度 \(O(n\log n)\)

Code
#include 
#define int long long

using namespace std;

namespace IO
{
    template 
    inline void read(T &x)
    {
        x = 0; int f = 1; char c = getchar();
        while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
        while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
        x *= f;
        return;
    }

    template 
    inline void write(T x)
    {
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 5e6 + 5;
int n, sa, sb, sc, sd, a[N], mod;
int b[N];

int F(int x)
{
    return (sa * x % mod * x % mod * x % mod + sb * x % mod * x % mod + sc * x % mod + sd) % mod;
}

bool chk(int x)
{
    memcpy(a, b, sizeof(b));
    a[1] -= x;
    for(int i = 2; i <= n; i++)
    {
        if(a[i - 1] <= a[i]) a[i] = max(a[i] - x, a[i - 1]);
        else if(a[i - 1] - a[i] > x) return false;
        else a[i] = a[i - 1];
    }
    return true;
}

signed main()
{
    read(n), read(sa), read(sb), read(sc), read(sd), read(a[1]), read(mod);
    for(int i = 2; i <= n; i++)
        a[i] = (F(a[i - 1]) + F(a[i - 2])) % mod;
    memcpy(b, a, sizeof(a));

    int l = 1, r = mod, ans;
    while(l <= r)
    {
        int mid = (l + r) >> 1;
        if(chk(mid)) r = mid - 1, ans = mid;
        else l = mid + 1;
    }
    write(ans), putchar('\n');
    
    return 0;
}
// A.S.

HNOI2012 矿场搭建

首先,如果有割点,那么两边的点肯定要分开讨论,这里把两边的点定义为“组”。

下面进行分类讨论:

  1. 如果该组内没有割点,那么至少设置两个出口
  2. 如果有一个割点,那么只需要设置一个。因为如果不是割点坍塌,可以走出去
  3. 如果有两个割点,就不需要设置,因为不管哪个坍塌,都可以走出去

方案数直接乘法原理就行了。

Code
#include 
#define ll long long
#define pb push_back

using namespace std;

namespace IO
{
    template 
    inline bool read(T &x)
    {
        x = 0; int f = 1; char c = getchar();
        while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
        while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
        x *= f;
        return x;
    }

    template 
    inline void write(T x)
    {
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 510;

int n, m, Case;
vector  g[N];
int dfn[N], low[N], tim, vis[N];
bool cut[N];

void tarjan(int u, int s)
{
    int ch = 0;
    dfn[u] = low[u] = ++tim;
    vis[u] = 1;
    for(auto v : g[u])
    {
        if(!dfn[v])
        {
            tarjan(v, s);
            low[u] = min(low[u], low[v]);
            if(low[v] >= dfn[u] && u != s) cut[u] = 1;
            if(u == s) ch++;
        }
        else if(vis[v]) low[u] = min(low[u], dfn[v]);
    }
    if(u == s && ch >= 2) cut[u] = 1;
    return;
}

ll ans, tot;
int id, num, cnt;

void dfs(int u)
{
    vis[u] = id;
    num++;
    for(auto v : g[u])
    {
        if(cut[v] && vis[v] != id)
            cnt++, vis[v] = id;
        if(!vis[v]) dfs(v);
    }
    return;
}

void Clear()
{
    tim = 0, id = 0;
    ans = 0, tot = 1;
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(vis, 0, sizeof(vis));
    memset(cut, 0, sizeof(cut));
    for(int i = 1; i <= n; i++) g[i].clear();
    n = 0;
}

int main()
{
    while(read(m))
    {
        Clear();

        for(int i = 1, u, v; i <= m; i++)
        {
            read(u), read(v);
            g[u].pb(v), g[v].pb(u);
            n = max(n, max(u, v));
        }
        
        for(int i = 1; i <= n; i++)
            if(!dfn[i]) tarjan(i, i);

        memset(vis, 0, sizeof(vis));
        for(int i = 1; i <= n; i++)
        {
            if(vis[i] || cut[i]) continue;
            id++, num = 0, cnt = 0;
            dfs(i);
            if(!cnt) ans += 2, tot *= (ll)num * (num - 1) / 2;
            else if(cnt == 1) ans++, tot *= (ll)num;
        }

        printf("Case %d: ", ++Case);
        write(ans), putchar(' ');
        write(tot), putchar('\n');
    }

    return 0;
}
// A.S.

Luogu P4198 楼房重建

见第一题


JOISC 2014 Day1 有趣的家庭菜园

树状数组+贪心

网上搜了一下,发现大部分题解是从小到大处理的,但是这样遇到相同的高度会很麻烦,所以这里从大到小进行计算。

对于第 \(i\) 株植物,因为我们从大到小枚举,所以在 \(i\) 前面的都是 \(>h_i\) 的,我们需要将 \(i\) 移动到最旁边,要么到左边,要么到右边。

发现可以用树状数组维护前缀和,能直接查询到左边需要交换几次,到右边的次数就是前面植物的个数减去到左边的次数。

对于高度相同的,要取出来后同统一查询答案,然后再统一插入树状数组,因为两边有高度相同也是合法的。

答案就是每株植物到两边的较小次数的和。

Code
#include 

using namespace std;

namespace IO
{
    template 
    inline void read(T &x)
    {
        x = 0; int f = 1; char c = getchar();
        while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
        while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
        x *= f;
        return;
    }

    template 
    inline void write(T x)
    {
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 3e5 + 5;
int n;
struct node
{
    int val, id;
    friend bool operator < (node x, node y)
    {
        return x.val > y.val;
    }
} a[N];
int c[N];

void add(int x, int y)
{
    for(; x <= n; x += x & -x)
        c[x] += y;
}

int qry(int x)
{
    int res = 0;
    for(; x; x -= x & -x)
        res += c[x];
    return res;
}

int main()
{
    read(n);
    for(int i = 1; i <= n; i++)
        read(a[i].val), a[i].id = i;
    sort(a + 1, a + 1 + n);

    long long ans = 0;
    for(int i = 1, pos = 1; i <= n; i++)
    {
        if(a[i].val != a[i - 1].val)
            for(; pos < i; pos++)
                add(a[pos].id, 1);
        int pre = qry(a[i].id);
        ans += min(pre, pos - 1 - pre);
    }
    write(ans), putchar('\n');

    return 0;
}
// A.S.

APIO2009 采油区域

神奇的 dp

dp 本身并不难,难在思路

因为要选 \(3\) 块,所以直接 dp 并不好做,考虑分别算出 \(3\) 块,将它们拼起来。

对于 \(n\times m\) 的矩形,处理出四个数组,分别表示 \((i,j)\) 左上、右上、左下、右下的 \(k\times k\) 的最大的矩形。

a | b
--|--
c | d

然后分六种情况讨论

  1. 上中下
  2. 左中右
  3. 左上+右上+下
  4. 左+右上+右下
  5. 左上+左下+右
  6. 上+左下+右下

代码看似很毒瘤,实际上都是重复的

还要说一下 luogu 的数据比较水,最好去 bzoj 上交

Code
#include 
#include 
#include 
#define int long long

using namespace std;

const int N = 1510;
int n, m, k, s[N][N];
int a[N][N], b[N][N], c[N][N], d[N][N];

/*
a | b
--|--
c | d
*/

int get_sum(int x, int y)
{
    return s[x][y] - s[x - k][y] - s[x][y - k] + s[x - k][y - k];
}

void calc()
{
    for(int i = k; i <= n; i++)
        for(int j = k; j <= m; j++)
            a[i][j] = max(get_sum(i, j), max(a[i - 1][j], a[i][j - 1]));
    for(int i = k; i <= n; i++)
        for(int j = m - k + 1; j >= 1; j--)
            b[i][j] = max(get_sum(i, j + k - 1), max(b[i - 1][j], b[i][j + 1]));
    for(int i = n - k + 1; i >= 1; i--)
        for(int j = k; j <= m; j++)
            c[i][j] = max(get_sum(i + k - 1, j), max(c[i + 1][j], c[i][j - 1]));
    for(int i = n - k + 1; i >= 1; i--)
        for(int j = m - k + 1; j >= 1; j--)
            d[i][j] = max(get_sum(i + k - 1, j + k - 1), max(d[i + 1][j], d[i][j + 1]));
}

int rd()
{
    int x = 0;
    char c = getchar();
    while(!isdigit(c)) c = getchar(), cout << c << endl;
    while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
    return x;
}

signed main()
{
    scanf("%lld%lld%lld", &n, &m, &k);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            scanf("%lld", &s[i][j]), s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
    calc();
    int ans = 0;
    for(int i = k; i <= n; i++)
        for(int j = k; j <= m; j++)
        {
            ans = max(ans, b[i - k][1] + get_sum(i, j) + d[i + 1][1]);
            ans = max(ans, c[1][j - k] + get_sum(i, j) + d[1][j + 1]);
        }
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
        {
            ans = max(ans, a[i][j] + b[i][j + 1] + c[i + 1][m]);
            ans = max(ans, a[n][j] + b[i][j + 1] + d[i + 1][j + 1]);
            ans = max(ans, a[i][j] + c[i + 1][j] + b[n][j + 1]);
            ans = max(ans, a[i][m] + c[i + 1][j] + d[i + 1][j + 1]);
        }
    printf("%lld\n", ans);
    return 0;
}
// A.S.

JSOI2007 建筑抢修

反悔贪心

\(t2\) 按升序排序,对于第 \(i\) 个,如果不能在 \(t2\) 内完成,那么前 \(i\) 个就最多只能修 \(i-1\) 个,贪心的取 \(t1\) 最小的就行了,只需要用堆维护一下。

Code
#include 

using namespace std;

namespace IO
{
    template 
    inline void read(T &x)
    {
        x = 0; int f = 1; char c = getchar();
        while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
        while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
        x *= f;
        return;
    }

    template 
    inline void write(T x)
    {
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 150005;
int n;
struct node
{
    int t1, t2;
    friend bool operator < (node x, node y)
    {
        return x.t2 < y.t2;
    }
} a[N];
priority_queue  que;
int T, ans;

int main()
{
    read(n);
    for(int i = 1; i <= n; i++)
        read(a[i].t1), read(a[i].t2);
    sort(a + 1, a + 1 + n);

    for(int i = 1; i <= n; i++)
    {
        if(T + a[i].t1 > a[i].t2)
        {
            if(a[i].t1 < que.top())
            {
                T -= que.top();
                que.pop();
                T += a[i].t1;
                que.push(a[i].t1);
            }
        }
        else
        {
            T += a[i].t1;
            que.push(a[i].t1);
            ans++;
        }
    }
    write(ans), putchar('\n');
    return 0;
}
// A.S.

JSOI2007 文本生成器

AC自动机上 dp

正难则反

用总数减去不合法的,dp 时只需要保证不经过单词的结尾。

\(f_{i,j}\) 表示长度为 \(i\),在AC自动机上走到了 \(j\) 的方案数,转移的时候判一下是不是单词的结尾就行了。

最后答案 \(ans=26^m-\sum_{i=1}^{tot}f_{m,i}\)

Code
#include
#include
#include
#include

using namespace std;

const int N=6010;
const int p=1e4+7;
char s[110];
int trie[N][30],tot,f[110][N];
bool val[N];

void ins(char s[])
{
	int len=strlen(s),c=0;
	for(int i=0;ique;
int fail[N];
void build()
{
	for(int i=0;i<26;i++)
		if(trie[0][i]) que.push(trie[0][i]);
	while(!que.empty())
	{
		int c=que.front();
		que.pop();
		for(int i=0;i<26;i++)
		{
			if(trie[c][i]) fail[trie[c][i]]=trie[fail[c]][i],val[trie[c][i]]|=val[fail[trie[c][i]]],que.push(trie[c][i]);
			else trie[c][i]=trie[fail[c]][i];
		}
	}
}

int qpow(int a,int b)
{
	int res=1;
	a%=p;
	while(b)
	{
		if(b&1) res=res*a%p;
		a=a*a%p;
		b>>=1;
	}
	return res;
}

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		ins(s);
	}
	build();
	f[0][0]=1;
	for(int i=0;i

HNOI2006 超级英雄

二分图最大匹配板子

Code
#include 
#include 
#include 

using namespace std;

const int N = 2010;
int n,m;
vector  G[N];
int tim, match[N],t[N],ans[N];

bool dfs(int u)
{
	for(int i = 0; i < G[u].size(); i++)
	{
		int v = G[u][i];
		if(t[v] == tim) continue;
		t[v] = tim;
		if(!match[v] || dfs(match[v]))
		{
			match[v] = u;
			ans[u] = v - m;
			return true;
		}
	}
	return false;
}

int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		G[i].push_back(m + x);
		G[i].push_back(m + y);
	}
	int sum = 0;
	tim = 1;
	for(int i = 1; i <= m; i++, tim++)
		if(dfs(i)) sum++;
		else break;
	printf("%d\n", sum);
	for(int i = 1; i <= sum; i++)
		printf("%d\n",ans[i]);
	return 0;
}
// A.S.

ZJOI2006 物流运输

观察到 \(n\le 100,m\le 20\)

这不随便写(?但其实还是挺难的

先考虑如何 dp

\(f_i\) 表示到第 \(i\) 天的最小花费

转移 \(f_i=min\{f_{j}+g_{j+1,i}\times (i-j)+k\}\)

表示第 \(j\) 天换路线,从 \(j+1\)\(i\) 天不换路线,其中 \(g_{j+1,i}\) 就表示其最小花费,这个是可以 \(O(n^3m)\) 枚举两天并去掉不能走的点,然后 \(O(n\log n)\) 跑最短路预处理的(够暴力吧

Code
#include 
#define ll long long

using namespace std;

namespace IO
{
    template 
    inline void read(T &x)
    {
        x = 0; int f = 1; char c = getchar();
        while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
        while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
        x *= f;
        return;
    }

    template 
    inline void write(T x)
    {
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 110;
int t, n, k, m, d;
struct edge
{
    int v, w, nxt;
} e[N * N];
int head[N], tot;

void link(int u, int v, int w)
{
    e[++tot] = (edge) {v, w, head[u]};
    head[u] = tot;
}

queue  que;
int dis[N];
bool vis[N], flag[N], cl[N][N];

void spfa()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    que.push(1), vis[1] = 1;
    while(!que.empty())
    {
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].v;
            if(flag[v]) continue;
            if(dis[v] > dis[u] + e[i].w)
            {
                dis[v] = dis[u] + e[i].w;
                if(!vis[v]) que.push(v), vis[v] = 1;
            }
        }
    }
    return;
}

ll g[N][N], f[N];

void dp()
{
    for(int i = 1; i <= t; i++)
    {
        f[i] = g[1][i] * i;
        for(int j = 1; j < i; j++)
            f[i] = min(f[i], f[j] + g[j + 1][i] * (i - j) + k);
    }
    return;
}

int main()
{
    read(t), read(n), read(k), read(m);
    for(int i = 1, u, v, w; i <= m; i++)
    {
        read(u), read(v), read(w);
        link(u, v, w), link(v, u, w);
    }
    read(d);
    for(int i = 1, p, a, b; i <= d; i++)
    {
        read(p), read(a), read(b);
        for(int j = a; j <= b; j++) cl[p][j] = 1;
    }

    for(int i = 1; i <= t; i++)
        for(int j = 1; j <= t; j++)
        {
            memset(flag, 0, sizeof(flag));
            for(int k = 1; k <= n; k++)
                for(int p = i; p <= j; p++)
                    if(cl[k][p]) flag[k] = 1;
            spfa();
            g[i][j] = dis[n];
        }

    dp();
    write(f[t]);

    return 0;
}
// A.S.