今日心路历程:今天又是很水的一天,最近总是会纠结自己到底该学什么,怎么学。主要牛客之路真的难啊,不仅仅是比赛时坐牢,后续的补题更加折磨。还有cf,每每打一场都让我觉得自己是个rz。
C. Scoring Subsequences
题目大意 :给定一个长度为n的非递减数组a,a(1~d)的得分定义为(a1*a2..^ad)/(d!)
对于一个序列,其代价为其所有子序列的最大得分的子序列最大长度。
对于k=1,2..n,对应(a(1~1),a(1~2)..a(1~n))求出每一个序列的代价。
思考:由于a为非递减序列,则最大得分的子序列一定为它的后缀序列。易得后缀序列的得分计算为(a1/k)*(a2/k-1)*...(ak/1)。由于得分要最大,所以后缀数组中的每一个元素都要大于等于1,二分最大长度,判断条件就是a[i-m+1]>=m。
int a[N];
void solve() {
int n;
cin >> n;
rep(i, 1, n) cin >> a[i];
vector<int>ret;
rep(i, 1, n) {
int l = 1, r = i;
while (l < r) {
int m = l + r + 1 >> 1;
if (a[i - m + 1] >= m) {
l = m;
}
else r = m - 1;
}
ret.push_back(l);
}
for (int x : ret) cout << x << ' ';
cout << endl;
}
C. Binary String Copying
题目大意:给定一个长度为n(只包含0|1的)字符串s,有m次操作,每次操作给定一个区间l,r,对字符串的该区间进行排序
生成一个副本字符串,问这m个副本字符串有多少个不同的。
思考:什么情况下字符串会改变呢,当区间内1在0的前面时就会使得副本跟原来的不同,怎么确定有多少个不同的副本呢,求出每一个位置的上一个0,和下一个1的位置,若l的下一个1位置在r的上一个0前面,那排序后肯定会发生改变(不变时也算一个新串,只是与原串相同,把l,r赋为-1)。然后用set纪录每一个10位置,最后输出set长度即可。
void solve() {
int n, m;
cin >> n >> m;
set<PII>st;
string s;
cin >> s;
vector<int>lst(n + 1, -1),nxt(n + 1, n); //上一个0(不包括本身),下一个1(包括本身)
for (int i = 0; i < n; i++) {
lst[i + 1] = s[i] == '0' ? i : lst[i];
}
for (int i = n - 1; i >= 0; i--) {
nxt[i] = s[i] == '1' ? i : nxt[i + 1];
}
while(m--) {
int l, r;
cin >> l >> r;
l--;
l = nxt[l];
r = lst[r];
// cout << l << ' ' << r << endl;
if(l > r) {
l = r = -1;
}
st.emplace(l, r);
}
cout << st.size() << endl;
}
P1433 吃奶酪
这个题很像一个TSP(旅行商)问题,而模拟退火计算解决这类问题的方法之一
题目问要吃完所有奶酪要跑多少距离,这个路径的所有情况就是1-n的全排列
所以模拟退火中生成新解的操作,就随机交换两个点就行了。新解的答案就是从(0,0)把新的排列走一遍,累加距离即可。
模拟退火 -- 主要用于求全局最优解
模拟退火的主要步骤:
1.先初始化温度,当前解和当前答案
2.如果温度小于最终温度,跳6;否则跳3
3.由当前解生成一个临时的新解,并计算新的答案
4.判断是否接受该临时解,接受则更新解和答案,不接受则回退到上个解
5.降温,跳2
6.结束
double x[20], y[20], ans = 1e9; //坐标, 最终答案
int n, now[20], st = clock(); //点的个数, 当前解, 卡时
const double t0 = 1e5; //初始温度
const double at = 1 - 3e-3; //变化速度
const double t_end = 1e-4; //最终温度
const int L = 100; //最大迭代次数
double Dis(int x1, int y1) { //计算距离
return sqrt(sqr(x[x1] - x[y1]) + sqr(y[x1] - y[y1]));
}
double calc() { //计算一个合法解的答案
double ret = 0;
for (int i = 1; i <= n; i++) {
ret += Dis(now[i], now[i - 1]);
}
return ret;
}
double Rand() { //生成随机小数
return (double)rand() / double(RAND_MAX);
}
bool check(double delta, double temp) { //判断是否接受新解
return (delta > 0 || Rand() < exp(delta / temp));
}
void SA() { //模拟退火
rep(i, 1, n) now[i] = i;
double t = t0, sum = calc();
while(t > t_end) {
for (int i = 0; i < L; i++) {
int u = rand() % n + 1, v = rand() % n + 1; //随机交换两个数
swap(now[u], now[v]);
double sum0 = calc();
if(check(sum - sum0, t)) { //判断是否接受新解
sum = sum0, ans = min(ans, sum);
}
else swap(now[u], now[v]);
}
t *= at; //降温
}
}
void solve() {
srand(0x7f); srand(rand());
cin >> n;
rep(i, 1, n) {
cin >> x[i] >> y[i];
}
while(clock() - st < 0.9 * CLOCKS_PER_SEC) SA(); //卡时
cout << SQR(2) << ans << endl;
}
P1120 小木棍
搜索 dfs + 剪枝
剪枝
1:所有小木棍长度的和能被答案木棍的长度整除
2:用桶来存储小木棍,每次枚举使用的小木棍长度时,从大到小,并且下次枚举要从当前使用长度的木棍开始。
3:枚举答案木棍长度的上界为所有小木棍长度的和除以2(这时候只能分成2根,否则最终答案为sum,只有一根),
下界为所有小木棍中最长的。
4:若某次搜索失败,而已拼长度为0,就直接返回,因为后面都是长度比他短的木棍,也同样凑不完。
若已拼长度与当前枚举长度之和为答案木棍长度,也直接返回,还是因为后面都比当前枚举的长度要短,显然现在是更优的情况
没必要再继续搜了。
int n;
int xmg[55];
int minn = 60, maxn;
int sum;
void dfs(int s, int len, int d, int nd) { //当前需要要拼的木棍数,所选答案木棍长度,当前已拼长度,当前使用的木棍长度
if (s == 0) { // 因为len是从小到大开始枚举的,所以第一个找到的就是最优答案
cout << len << endl;
exit(0);
}
if (len == d) { //拼好一根,拼下一根
dfs(s - 1, len, 0, maxn);
return;
}
for (int i = nd; i >= minn; i--) { //2
if (xmg[i] && d + i <= len) {
xmg[i]--;
dfs(s, len, d + i, i);
xmg[i]++;
if (d == 0 || d + i == len) //4
return;
}
}
}
void solve() {
cin >> n;
rep(i, 1, n) {
int d;
cin >> d;
sum += d;
xmg[d]++; //2
if (maxn < d) maxn = d;
if (minn > d) minn = d;
}
for (int i = maxn; i <= sum / 2; i++) { //3
if (sum % i == 0) { //1
dfs(sum / i, i, 0, maxn);
}
}
cout << sum << endl;
}
C. Sum on Subarrays
题目大意:给定n,k,构造一个长度为n,有k个子数组之和为正数的数组a。(n <= 30 && |a[i]| <= 1000 )
若k<n, 这种情况很简单,就是把的第k个数前面的数赋成负数(这里用-1方便),第k个数赋成比前k-1个数之和的绝对值大的正数(这里用200),
然后把k+1个数后面的数赋成-1,第k+1个数赋成绝对值比第k个数大的负数(这里用-400)。
思考:k>=n怎么做,我们是不是可以先获得n个目标子数组呢,然后再考虑剩下的,没错就是递归。
把当前操作的末尾a[n]赋为一个最大值,保证可以获得n个目标子数组,然后递归(n - 1, k - n)。
int a[35];
void sol(int n, int k) {
if (n == 0) return;
if (k < n) {
rep(i, 1, n) a[i] = -1;
a[k] = 200;
a[k + 1] = -400; //获得个k个和为正的子数组
}
else {
sol(n - 1, k - n);
a[n] = 1000; //获得个n个和为正的子数组
}
}
void solve() {
int n, k;
cin >> n >> k;
sol(n, k);
rep(i, 1, n) cout << a[i] << ' ';
cout << endl;
}
P1441 砝码称重
这题上次用dfs+dp做的,结果发现也可以剪枝过!!
int n, m;
bool vis[2005];
int a[25];
int stk[25]; //存放被选中的砝码
int ans, ret;
bool d[25][2005]; //d[i][j]标记选了前i个砝码后可以称的重量
void dfs(int nber, int sum) {
if(nber > (n - m)) {
if(!vis[sum] && sum) {
vis[sum] = true;
ans++;
}
return;
}
if(d[nber][sum]) return; //绝妙的剪枝
d[nber][sum] = true;
dfs(nber + 1, sum + stk[nber + 1]);
dfs(nber + 1, sum);
}
bool pv[25];
void dfs1(int now, int la) { //选出n-m个砝码
if(now == (n - m)) { //在选出的砝码里找答案
ans = 0;
mems(vis, false);
mems(d, false);
dfs(0, 0);
ret = max(ans, ret);
return;
}
for (int i = la + 1; i <= n; i++) {
if(pv[i]) continue;
stk[++now] = a[i];
pv[i] = true; //i号要了
dfs1(now, i);
now--;
pv[i] = false; //回溯
}
}
void solve() {
cin >> n >> m;
rep(i, 1, n) cin >> a[i];
sort(a + 1, a + 1 + n);
dfs1(0, 0);
cout << ret << endl;
}
继续努力!!!