一月补题录

P2569 [SCOI2010] 股票交易

我记得有这么一个结论:一次性买完或者一次性卖完是最优的,但是在这道题里似乎不适用。

我们看,数据范围只有 \(2000\),直接想到设 \(dp_{i,j}\) 表示如果在第 \(i\) 天有 \(j\) 支股票的最大收益。那么转移方程就有:

\[dp_{i,j} = \max\begin{cases} dp_{i - 1,j} & \text{不买} \\ -AP_i \times j & \text{凭空买} \\ dp_{i - w - 1,k} - (j - k) \times AP_i & \text{在之前的基础上买} \\ dp_{i - w - 1,j} + (k - j) \times BP_i & \text{在之间的基础上卖} \end{cases} \]

然后来看看复杂度,设 \(\text{MaxP}\)\(n\) 同阶。枚举状态 \(\mathcal{O}(n^2)\),转移 \(\mathcal{O}(n)\),合起来 \(\mathcal{O}(n^3)\),不太行啊。我们直接单调队列优化第 \(3,4\) 个转移方程,这道题就做完了。

code:

#include<bits/extc++.h>
using namespace std;
const int maxn = 2005;
int t,mxp,w;
int ap[maxn],bp[maxn],as[maxn],bs[maxn];
int dp[maxn][2005];
deque<int>q;
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> t >> mxp >> w;
    for (int i = 1; i <= t; i++)
        cin >> ap[i] >> bp[i] >> as[i] >> bs[i];
    memset(dp,~0x3f,sizeof dp);
    for (int i = 1; i <= t; i++)
    {
        for (int j = 0; j <= as[i]; j++)
            dp[i][j] = -j * ap[i];
        for (int j = 0; j <= mxp; j++)
            dp[i][j] = max(dp[i][j],dp[i - 1][j]);
        if (i <= w)
            continue;
        q.clear();
        for (int j = 0; j <= mxp; j++)
        {
            while (!q.empty() && q.front() < j - as[i])
                q.pop_front();
            while (!q.empty() && dp[i - w - 1][q.back()] + q.back() * ap[i] <= dp[i - w - 1][j] + j * ap[i])
                q.pop_back();
            if (!q.empty())
                dp[i][j] = max(dp[i][j],dp[i - w - 1][q.front()] + q.front() * ap[i] - j * ap[i]);
            q.push_back(j);
        }
        q.clear();
        for (int j = mxp; j >= 0; j--)
        {
            while (!q.empty() && q.front() > j + bs[i])
                q.pop_front();
            while (!q.empty() && dp[i - w - 1][q.back()] + q.back() * bp[i] <= dp[i - w - 1][j] + j * bp[i])
                q.pop_back();
            if (!q.empty())
                dp[i][j] = max(dp[i][j],dp[i - w - 1][q.front()] + q.front() * bp[i] - j * bp[i]);
            q.push_back(j);
        }
    }
    int ans = 0;
    for (int i = 0; i <= mxp; i++)
        ans = max(ans,dp[t][i]);
    cout << ans;
    return 0;
}

Knights of the Round Table

首先,有个从题解里看到的结论:在一个点双连通分量里,如果有奇环,那么每个点都一定在一个奇环上。证明如下:

取两个在奇环上的点 \(x,y\) 和一个不在奇环上的点 \(z\),使得 \(x \rightarrow z\)\(z \rightarrow y\) 都有路径。那么 \(x\)\(y\) 之间就一定有一条长度为奇数的路径和一条长度为偶数的路径。如果 \(x \rightarrow z \rightarrow y\) 的长度为奇数,那么就可以和 \(x \rightarrow y\) 长度为偶数的路径组成一个奇环,反之就可以和长度为奇数的路径组成一个奇环。

那么这道题就简单了。我们只需要建反图,然后 tarjan 把所有的点双连通分量找出来,然后看看里面有没有奇环。记录一个 \(f\) 数组,\(f_i\) 表示点 \(i\) 对答案的贡献,初始全部为 \(1\)。在这个点双里面,如果有奇环,那么就把这个点双里面所有点的贡献全部置为 \(0\)。这么做是因为点双之间可能有重复的点,这么做可以保证不算重复。

放下代码:

#include<cstdio>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
const int maxn = 1005;
int n,m,tim,dc;
bool bel[maxn],g[maxn][maxn],flag;
int head[maxn],idx = 1;
int dfn[maxn],low[maxn];
int siz[maxn],col[maxn],f[maxn];
stack<int>st;
vector<int>dcc[maxn];
inline void init()//初始化
{
    fill(dfn + 1,dfn + n + 1,0);
    fill(low + 1,low + n + 1,0);
    fill(head + 1,head + n + 1,0);
    fill(siz + 1,siz + n + 1,0);
    fill(f + 1,f + n + 1,1);
    for (int i = 1; i <= n; i++)
    {
        fill(g[i] + 1,g[i] + n + 1,1);
        g[i][i] = 0;
    }
    tim = dc = 0;
}
void dfs1(int u)//tarjan找点双
{
    dfn[u] = low[u] = ++tim;
    st.push(u);
    for (int v = 1; v <= n; v++)
    {
        if (!g[u][v])
            continue;
        if (!dfn[v])
        {
            dfs1(v);
            low[u] = min(low[u],low[v]);
            if (dfn[u] <= low[v])
            {
                int lst;
                dcc[++dc].clear();
                do
                {
                    lst = st.top();
                    st.pop();
                    dcc[dc].push_back(lst);
                } while (lst != v);
                dcc[dc].push_back(u);
            }
        }
        else
            low[u] = min(low[u],dfn[v]);
    }
}
void dfs2(int u)//二分图染色,看有没有奇环
{
    for (int v = 1; v <= n; v++)
    {
        if (bel[v] && g[u][v])//bel[i]表示是不是在这个点双里
        {
            if (!col[v])
            {
                col[v] = 3 - col[u];
                dfs2(v);
            }
            else if (col[v] == col[u])//无法染色就是有奇环
                flag = 1;
        }
    }
}
inline void solve()
{
    init();
    int u,v;
    while (m--)
    {
        scanf("%d%d",&u,&v);
        g[u][v] = g[v][u] = 0;//建反图
    }
    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            dfs1(i);
    for (int i = 1; i <= dc; i++)
    {
        fill(bel + 1,bel + n + 1,0);
        fill(col + 1,col + n + 1,0);
        for (vector<int>::iterator j = dcc[i].begin(); j != dcc[i].end(); j++)
            bel[*j] = 1;//因为是poj所以只能用可恶的iterator
        col[dcc[i][0]] = 1;
        flag = 0;
        dfs2(dcc[i][0]);
        if (!flag)
            continue;
        for (vector<int>::iterator j = dcc[i].begin(); j != dcc[i].end(); j++)
            f[*j] = 0;//将贡献置为0
    }
    int ans = 0;
    for (int i = 1; i <= n; i++)
        ans += f[i];
    printf("%d\n",ans);
}
signed main()
{
    scanf("%d%d",&n,&m);
    while (n || m)
    {
        solve();
        scanf("%d%d",&n,&m);
    }
    return 0;
}

吐槽:这poj能不能升级一下编译器啊,不能用万能头就算了嘛,你连 for(auto i : vector<int>()) 都用不了,实在是太麻烦了。

posted @ 2025-01-28 11:08  伊埃斯  阅读(17)  评论(0)    收藏  举报