2023.7.29学习记录

今日心路历程:今天又是很水的一天,最近总是会纠结自己到底该学什么,怎么学。主要牛客之路真的难啊,不仅仅是比赛时坐牢,后续的补题更加折磨。还有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;
}

继续努力!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

akb000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值