一月补题录
P2569 [SCOI2010] 股票交易
我记得有这么一个结论:一次性买完或者一次性卖完是最优的,但是在这道题里似乎不适用。
我们看,数据范围只有 \(2000\),直接想到设 \(dp_{i,j}\) 表示如果在第 \(i\) 天有 \(j\) 支股票的最大收益。那么转移方程就有:
然后来看看复杂度,设 \(\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>()) 都用不了,实在是太麻烦了。
本文来自博客园,使用 CC BY-NC-SA 4.0 协议。
作者:伊埃斯,转载请注明原文链接:https://www.cnblogs.com/Eous/p/18693035

浙公网安备 33010602011771号