查看题目
n个气球,每个气球高度为正整数。给你每两个气球之间的高度和,还原出所有气球的高度,保证答案唯一。
签到题,因为高度的范围不大,直接枚举第一个气球的高度,检测其与第二,三个气球的关系是否合法。需注意因为答案唯一所以只有两个气球的时候高度肯定都为1.
#pragma GCC optimize(2)
#include
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 1005
using namespace std;
typedef long long ll;
const ll mod=998244353;
const double eps=1e-9;
const double PI=acos(-1.0);
int n, num[maxn], s[maxn][maxn], x;
int main()
{
cin>>n;
for (int i=1; i<=n; i++)
for (int j=1; j<=n; j++)
cin>>s[i][j];
for (x=1; x<=100000; x++)
if (s[1][2]-x+s[1][3]-x==s[2][3]) break;
if (n==2) x=1;
for (int i=1; i<=n; i++)
cout<<abs(s[1][i]-x)<<" ";
return 0;
}
给一个n个点m条边的无向图,要求你对每条边指定一个方向,使原图成为一个有向无环图,且最长路最短,输出最短的最长路。
有个结论,给点染色,有边直接相连的点颜色不能一样,然后答案就是最少染色数-1。
所以用状压dp或者直接深搜找出无向图的最少染色数就行了。
关于结论的证明:其本质就是“狄尔沃斯定理”,定理指出:对于任意有限偏序集,其最长链中元素的数目必等于其最小反链划分中反链的数目。
狄尔沃斯定理的证明可以自行百度。
#pragma GCC optimize(2)
#include
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 1005
using namespace std;
typedef long long ll;
const ll mod=998244353;
const double eps=1e-9;
const double PI=acos(-1.0);
vector<int> edge[maxn];
int n, m, ans=inf, col[maxn];
int check(int now, int c)
{
for (auto to: edge[now])
if (c==col[to]) return 0;
return 1;
}
void dfs(int now, int res)
{
if (now>n) {ans=min(res, ans); return ;}
if (res>=ans) return ;
for (int i=1; i<=res+1; i++)
{
if (!check(now, i)) continue;
col[now]=i;
if (i==res+1)
{
res++;
dfs(now+1, res);
res--;
}
else dfs(now+1, res);
col[now]=0;
}
}
int main()
{
cin>>n>>m;
for (int i=1, u, v; i<=m; i++)
{
cin>>u>>v;
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(1, 0);
cout<<ans-1;
return 0;
}
两人下棋,棋盘大小为n*m,每个格子要么是黑色的要么是白色的。两人轮流操作,操作者可以选择一个黑色格子,并将以其为右下角,棋盘左上角为左上角的矩形中的所有格子颜色翻转,若无黑格可选,则当前操作者输。问先手者会赢还是输。
签到+大力猜测题。由于每次翻转都会导致棋盘左上角被翻转,因此若左上角开局是黑色的,后手者操作完左上角一定还是黑色的,因此先手者一定胜。若为白则先手者操作完左上角一定为黑,则后手者必胜。所以直接判断左上角颜色就行了。
#include
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
#define inf 1e9+1
#define Inf 223372036854775807
#define maxn 1010
#define eps 1e-8
const ll mod=998244353;
string s[maxn];
int T, n, m;
int main()
{
cin>>T;
while (T--)
{
cin>>n>>m;
for (int i=1; i<=n; i++)
cin>>s[i];
if (s[1][0]=='1') cout<<"call\n";
else cout<<"aoligei\n";
}
return 0;
}
有n个人站成一排,第i个人有一个权值ai,要把他们分成恰好k段并使每段内两两权值相同的对数之和最少。
决策单调性+整体二分转移+莫队
超出能力范围了,不是很清楚这样写算不算有点套路的东西?反正确实是想不到…
决策单调性的证明可以看这位大佬的博客
总的来说,发现决策单调性后就可以通过类似整体二分的方法。
假设最优转移点的区间是[L, R],寻找[l, r]区间内每个点的最优转移点时可以先寻找到区间中点mid的最优转移点p,然后区间内左半段的点的最优转移点肯定在[L, p],右半段的点的最优转移点肯定在[p+1, R]。然后答案的统计利用莫队的思想来实现。
具体可以看代码吧,感觉对这道题解法的理解还有待加深。
#pragma GCC optimize(3)
#include
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 100010
using namespace std;
typedef long long ll;
const ll mod=998244353;
const double eps=1e-9;
const double PI=acos(-1.0);
ll cnt[maxn], num[maxn], ans;
ll lp=1, rp=0, dp[21][maxn];
ll n, k;
void add(ll x)
{
ans+=cnt[x];
cnt[x]++;
}
void red(ll x)
{
cnt[x]--;
ans-=cnt[x];
}
void md(int l, int r)
{
while (lp>l) add(num[--lp]);
while (lp<l) red(num[lp++]);
while (rp>r) red(num[rp--]);
while (rp<r) add(num[++rp]);
}
void cal(int l, int r, int L, int R, int k)
{
if (l>=r)
{
for (int i=L; i<=R; i++)
{
md(i, l);
if (dp[k][l]>dp[k-1][i-1]+ans)
dp[k][l]=dp[k-1][i-1]+ans;
}
return ;
}
int mid=(l+r)>>1, p=L;
for (int i=L; i<=min(R, mid); i++)
{
md(i, mid);
if (dp[k][mid]>dp[k-1][i-1]+ans)
dp[k][mid]=dp[k-1][i-1]+ans, p=i;
}
cal(l, mid, L, p, k);
cal(mid+1, r, p, R, k);
}
int main()
{
cin>>n>>k;
for (int i=1; i<=n; i++)
cin>>num[i];
memset(dp, 0x3f, sizeof(dp));
for (int i=1; i<=n; i++)
{
add(num[++rp]);
dp[1][i]=ans;
}
for (int i=2; i<=k; i++)
{
dp[i][0]=0;
cal(1, n, 1, n, i);
}
cout<<dp[k][n];
return 0;
}
给一颗n个节点的树,其中有k个节点是需要到达的,求分别以1,2…n为起点时经过k个节点的路径长度(不需要回到起点)。
从简考虑,假设没有k这个条件,每个点都要求到达的情况。
假设起点固定,比如说1号点为起点,要求出此时的答案,稍加观察就可以发现,最优情况下应该是从1号点到以1为根的树中最深点的路径上的边走一遍,其它边走两遍,这个最多动手画一下就能感受出来。
也就是1号点的答案就是所有边长度和乘2再减去最深点的深度。对其它点也是如此,因此我们只要能找到以i号点为根的情况下最深点的深度,就可以求出答案。
如果我们先求出了一号点的答案,并且记录了各个点在以一号点为根的情况下的深度,我们就可以对答案进行转移。
稍加观察可以发现,点的最深路径可以转移给它的儿子。
第一种情况:如果这个儿子不在其父亲到最深点的路径上,那么以这个点为根时最深点一定仍是原来那个点(因为最深的路径又加上了一条边)。
第二种情况:如果这个儿子在其父亲到最深点的路径上,那么就需要判断之前的最深路径是否还是最深的(因为少了一条边),具体而言,我们除了要记录每个点往下最深点,还要记录次深点。
第二种情况可以参考下图,当由fi转移到i时最深路径的三种情况。
现在再加入k这个条件,不难发现如果我们先找出这k个点的“虚树”(这里可能不能叫虚树,应该是包含了这k个点以及他们之间两两路径的树),其它点的答案就等于其到“虚树”最近距离的点的答案加上两者之间的距离。
(实现过程:大力dfs
#include
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
#define inf 1e9+1
#define Inf 223372036854775807
#define maxn 501000
#define eps 1e-8
const ll mod=998244353;
struct bian
{
ll to, v;
};
ll n, K, root, go[maxn], col[maxn], ans[maxn];
vector<bian> E[maxn], edge[maxn];
ll sdp[maxn], mxdp[maxn], sedp[maxn], mxdpt[maxn];
ll dfs(ll now, ll fa)
{//找出“虚树”,建边
ll f=0;
if (go[now]) f=1;
for (auto i: E[now])
{
if (i.to==fa) continue;
if (dfs(i.to, now))
{
f=1;
edge[now].push_back((bian){i.to, i.v});
edge[i.to].push_back((bian){now, i.v});
}
}
if (f) go[now]=1;
return f;
}
void dfs2(ll now, ll fa)
{//找出 不在虚树上的点 的 虚树上最近的点 是哪个
if (go[now]) col[now]=now;
for (auto i: E[now])
{
if (i.to==fa) continue;
if (!go[i.to])
{
ans[i.to]=ans[now]+i.v;
col[i.to]=col[now];
}
dfs2(i.to, now);
}
}
void dfs3(ll now, ll fa)
{
for (auto i: edge[now])
{
if (i.to==fa) continue;
dfs3(i.to, now);
sdp[now]+=sdp[i.to]+i.v;
if (mxdp[i.to]+i.v>mxdp[now])
sedp[now]=mxdp[now], mxdp[now]=mxdp[i.to]+i.v, mxdpt[now]=i.to;
else if (mxdp[i.to]+i.v>sedp[now]) sedp[now]=mxdp[i.to]+i.v;
}
}
void dfs4(ll now, ll fa)
{
ans[now]=-mxdp[now];
for (auto i: edge[now])
{
if (i.to==fa) continue;
if (i.to!=mxdpt[now])
{
mxdpt[i.to]=now;
mxdp[i.to]=mxdp[now]+i.v;
dfs4(i.to, now);
continue;
}
if (sedp[now]+i.v>sedp[i.to]) sedp[i.to]=sedp[now]+i.v;
if (i.to==mxdpt[now])
{
if (mxdp[i.to]<=sedp[i.to])
{
mxdpt[i.to]=now;
mxdp[i.to]=sedp[i.to];
dfs4(i.to, now);
}
else
{
dfs4(i.to, now);
}
}
}
}
int main()
{
cin>>n>>K;
for (ll i=1, u, v, w; i<n; i++)
{
cin>>u>>v>>w;
E[u].push_back((bian){v, w});
E[v].push_back((bian){u, w});
}
for (ll i=1, x; i<=K; i++)
{
cin>>x;
go[x]=1;
root=x;
}
dfs(root, 0);
dfs2(root, 0);
dfs3(root, 0);
ans[root]=-mxdp[root];
dfs4(root, 0);
for (ll i=1; i<=n; i++)
{
if (go[i]) cout<<2*sdp[root]+ans[i]<<"\n";
else cout<<ans[i]+2*sdp[root]+ans[col[i]]<<"\n";
}
return 0;
}