一道拓扑排序的题,一开始我是正向建边的,但是一直wa,后来想明白了正向建边无法满足题目字典序最小的要求,比如有一个条件是 4比2小 ,正向建边的做法得出的结果是1 4 2 3 但是 1 3 4 2 的字典序显然更小且符合条件。这道题虽然比较简单,但是这种小细节如果思考不全面还是比较容易犯错误的。
//由于题目的数据量比较小,所以算法的复杂度为O(n*n)
//可以将入度为0的点搞一个集合(set),优化到O(nlogn)
#include
#include
#define fre(f) freopen(f ".in", "r", stdin), freopen(f ".out", "w", stdout)
const int N = 205;
using namespace std;
int ind[N], ans[N];
bool vis[N];
vector<int> v[N];
void init()
{
for (int i = 0; i < N; i++)
{
ind[i] = 0;
v[i].clear();
vis[i] = 0;
}
}
int main()
{
// fre("A");
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
bool sign = 1;
int n, m, x, y;
cin >> n >> m;
init();
while (m--)
{
cin >> x >> y;
ind[x]++;
v[y].push_back(x);
}
for (int i = n; i >= 1; i--)
{
bool flag = 0;
for (int j = n; j >= 1; j--)
{
if (vis[j]) continue;
if (ind[j] == 0)
{
vis[j] = 1;
ans[j] = i;
for (int k = 0; k < v[j].size(); k++)
ind[v[j][k]]--;
flag = 1;
break;
}
}
if (!flag)
{
sign = 0;
break;
}
}
if (sign)
for (int i = 1; i <= n; i++)
cout << ans[i] << ' ';
else
cout << "-1";
cout << endl;
}
return 0;
}
一道图论较难的题 对我而言是这样
用到了tarjan算法和缩点的思想。
tarjan算法套模板就行。建图选中链式前向星或者邻接表都可以,邻接表更好写。
正向建图,如果这幅图中存在出度为0的点且唯一(这里的点是指缩点后的集合),那么这个点就是答案。这题的缩点比较简单,将同一个强连通分量里的点放到一个集合里就行。
#include
#include
#include
#include
#include
#define mem(a, v) memset(a, v, sizeof(a))
const int N = 1e4 + 5;
using namespace std;
int out[N], dfn[N], low[N], cnt[N], f[N], t = 1, ans = 0;
bool vis[N];
stack<int> s;
vector<int> g[N];
void tarjan(int x)
{
dfn[x] = low[x] = ++t;
s.push(x);
vis[x] = 1;
for (int i = 0; i < g[x].size(); i++)
{
int y = g[x][i];
if (!dfn[y])
{
tarjan(y);
low[x] = min(low[x], low[y]);
}
if (vis[y])
low[x] = min(low[x], low[y]);
}
if (low[x] == dfn[x])
{
ans++;
int top;
while (s.size())
{
top = s.top();
s.pop();
vis[top] = 0;
f[top] = ans; //将同一个强连通分量里的点放入一个集合中,达到缩点的效果(将一个强连通分量看成一个点)
cnt[ans]++; //记录这个集合的点数
if (top == x) break;
}
}
}
int main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, m, x, y;
mem(out, 0), mem(dfn, 0), mem(low, 0), mem(cnt, 0), mem(vis, 0);
cin >> n >> m;
while (m--)
{
cin >> x >> y;
g[x].push_back(y);
}
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
for (int i = 1; i <= n; i++) //计算出度
{
for (int j = 0; j < g[i].size(); j++)
{
int k = g[i][j];
if (f[i] == f[k]) continue; //因为将强连通分量看作一个点,因此在同一个强连通分量里的两个点就略过
out[f[i]]++;
}
}
int sum = 0, outs = 0;
for (int i = 1; i <= ans; i++)
{
if (out[i] == 0)
{
outs++;
sum = cnt[i];
}
}
if (outs == 1) //如果有多个出度为0的点,就不符合条件
cout << sum;
else
cout << 0;
return 0;
}
2021年的寒假就做到了这道题,那时刚接触算法,看了一天的题解才把这题过了。如今再次遇到这题依然被卡了 就记录一下吧
#include
#include
#include
#define mem(a, v) memset(a, v, sizeof(a))
#define fre(f) freopen(f ".in", "r", stdin)
using namespace std;
int stick[100], n, ans;
bool vis[100];
bool cmp(int a, int b) { return a > b; }
bool dfs(int s, int rest, int cnt)
{
if (rest == 0) rest = ans, s = 0;
if (cnt == n) return 1;
int last = -1;
for (int i = s; i < n; i++)
{
if (stick[i] == last || vis[i] || stick[i] > rest) continue;
vis[i] = 1;
if (dfs(i + 1, rest - stick[i], cnt + 1)) return 1;
vis[i] = 0;
//剪枝:跳过长度一样的棍子
last = stick[i];
//剪枝:如果在构建新的棍子时第一根棍子就用不上了 那么后面这根棍子也是用不上的 所以直接退出
if (rest == ans) return 0;
}
return 0;
}
int main()
{
while (cin >> n && n)
{
mem(vis, 0);
int sum = 0;
for (int i = 0; i < n; i++)
{
cin >> stick[i];
sum += stick[i];
}
sort(stick, stick + n, cmp);
for (int i = stick[n - 1]; i <= sum; i++)
{
//剪枝:初始棍子的长度必须是长度和的因子
if (sum % i) continue;
ans = i;
if (dfs(0, ans, 0))
{
cout << ans << endl;
break;
}
}
}
return 0;
}
第一次做字典树的题,用动态内存分配的方法总是超时,改用静态数组就过了。因此下次做这类题还是用静态数组比较好。
主要判断两个:1、前缀不能包含某个字符串 2、自己不能被某个字符串的前缀包含
#include
#include
#define N 100005
#define mem(a, v) memset(a, v, sizeof(a))
#define fre(f) freopen(f ".in", "r", stdin)
using namespace std;
int next[N][10], sz;
bool sign[N]; //记录是否为字符串的最后一个位置
int main()
{
int T, n;
char s[20];
scanf("%d", &T);
while (T--)
{
mem(next, 0);
mem(sign, 0);
sz = 0;
scanf("%d", &n);
bool flag = 1;
while (n--)
{
scanf("%s", s);
if (flag)
{
int len = strlen(s), it = 0;
bool tmp = 0;
for (int i = 0; i < len; i++)
{
char c = s[i];
if (next[it][c - '0'] == 0) //不能被某个字符的前缀包含
{
tmp = 1;
next[it][c - '0'] = ++sz;
}
it = next[it][c - '0'];
if (sign[it]) flag = 0; //前缀不能包含某个字符串
}
sign[it] = 1;
if (!tmp) flag = 0;
}
}
if (flag)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
这题理解错了题意,其实是一道很简单的题。每次都可以出现k个不同的数字,而不是一共出现k个不同的数字,那么每次都可以消除k-1个数字。然后再特判一下就好了。
#include
using namespace std;
const int maxn = 5e2 + 5;
int vis[maxn], a[maxn];
int main()
{
int t;
cin >> t;
while (t--)
{
memset(vis, 0, sizeof(vis));
int n, k, sum = 0;
cin >> n >> k;
for (int i = 0; i < n; i++)
{
cin >> a[i];
if (vis[a[i]] == 0) //统计出现了几个不同的数字
{
vis[a[i]] = 1;
sum++;
}
}
if (k == 1 && sum > 1)
cout << -1 << endl;
else if (sum <= k)
cout << 1 << endl;
else
{
if ((sum - k) % (k - 1) == 0)
cout << 1 + (sum - k) / (k - 1) << endl;
else
cout << 2 + (sum - k) / (k - 1) << endl;
}
}
return 0;
}
思路都在代码注释里了,个人感觉是一道比较好的题
//无需每次都更新 只要将所有的改变进行合并即可
#include
#include
#include
#define maxn 200005
#define mem(a) memset(a, 0, sizeof(a))
#define fre(f) freopen(f ".in", "r", stdin)
using namespace std;
//last[i]表示最后一次改变balance是在第i次救济之前
//maxp[i]表示第i次救济之后中最大的一次救济
int last[maxn], payoff[maxn], maxp[maxn], balance[maxn];
int main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, q, cnt = 1; //cnt记录出现了几次payoff
cin >> n;
for (int i = 1; i <= n; i++)
cin >> balance[i], last[i] = cnt;
cin >> q;
while (q--)
{
int op, x, y;
cin >> op;
if (op == 1)
{
cin >> x >> y;
last[x] = cnt;
balance[x] = y;
}
else
{
cin >> x;
payoff[cnt] = x;
cnt++;
}
}
maxp[cnt - 1] = payoff[cnt - 1];
for (int i = cnt - 2; i >= 0; i--)
maxp[i] = max(payoff[i], maxp[i + 1]);
for (int i = 1; i <= n; i++)
{
balance[i] = max(balance[i], maxp[last[i]]);
cout << balance[i] << ' ';
}
return 0;
}
一道比较麻烦的题,需要考虑较多的情况,对思维也有一点要求。对括号的处理比较关键
#include
#define maxn 10005
using namespace std;
int pos;
char str[maxn];
bool cal()
{
++pos;
if (str[pos] != '(') //计算普通值
{
if (str[pos] == '!')
return !cal();
else if (str[pos] == 'V')
return 1;
else if (str[pos] == 'F')
return 0;
}
else //计算括号
{
bool res;
++pos;
if (str[pos] == '!')
res = !cal();
else if (str[pos] == 'V')
res = 1;
else if (str[pos] == 'F')
res = 0;
else if (str[pos] == '(')
{
--pos;
res = cal();
}
++pos;
while (true)
{
if (str[pos] == '|')
res |= cal();
else if (str[pos] == '&')
res &= cal();
else if (str[pos] == ')')
return res;
++pos;
}
}
}
int main()
{
str[0] = '(';
char temp[maxn];
int t = 1;
while (cin.getline(temp, maxn))
{
//去掉空格
int index = 0;
for (int i = 0; temp[i] != '\0'; i++)
if (temp[i] != ' ') str[++index] = temp[i];
str[++index] = ')';
pos = -1;
for (int i = 0;; i++)
{
if (str[i] == 0)
{
str[i] = ')';
str[i + 1] = 0;
break;
}
}
cout << "Expression " << t++ << ": ";
if (cal())
cout << "V\n";
else
cout << "F\n";
}
return 0;
}
题目的意思是给一个完全图去掉一些边,然后找出有几个连通集,以及每个连通集的大小。自己的做法给卡了挺久,看了别人的代码后想明白了(换了一种做法)。
思路在代码注释中体现了。
#include
#include
#include
#include
#include
#define maxn 200005
using namespace std;
vector<int> g[maxn], ans;
set<int> s1, s2;
//先找与v连接的那一圈点,然后再找与那一圈点连接的点,不断重复
void bfs(int v)
{
queue<int> q;
int cnt = 1;
s1.erase(v);
q.push(v);
while (q.size())
{
v = q.front();
q.pop();
for (int a : g[v]) //所有与v点没有连边的点都去除,暂时存放到s2中,s1中剩下的点肯定与v相连
{
if (s1.count(a))
{
s2.insert(a);
s1.erase(a);
}
}
cnt += s1.size();
for (int a : s1) //然后将与v相连的点全部入队
q.push(a);
// s1清空并将s2中的点转移回s1中
s1 = s2;
s2.clear();
//进行下一轮循环,查找剩下的点是否还有相连的
}
ans.push_back(cnt);
}
int main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, m;
int x, y;
cin >> n >> m;
for (int i = 1; i <= n; i++)
s1.insert(i);
for (int i = 0; i < m; i++)
{
cin >> x >> y;
g[x].push_back(y);
g[y].push_back(x);
}
for (int i = 1; i <= n; i++)
{
if (s1.count(i))
bfs(i);
}
sort(ans.begin(), ans.end());
cout << ans.size() << endl;
for (int a : ans)
cout << a << " ";
return 0;
}