转变思想
(1)- 动态规划专题。从一开始没头绪,到后来慢慢能做对一些简单的dp。: 70
(2)- 从不会暴力,到首先打暴力,写复杂度不优的算法然后想优化。 :0
(1)- 有了手感 :170
(2)- 上路 : 200
但是还有很多要改进的地方
10月15日~10月20日
(1)- 数论第一次:170 -> 190; COUT PRINTF(“%D”,);最后输出的时候要记得换成printf不然T
递推: 与计数要加强。 一定要记得把输出调试删除,程序所不需要的修改要标注。
没有头绪先打表。
一开始dp专题 ,觉得很沮丧,最简单的背包问题都做不出来。那天中午猛刷了很多背包水题。
发现黄题,橙题,绿题也有很多新奇的地方,所以这一周都在刷这类题。
P2347 砝码称重:
#include
using namespace std;
const int N = 1e3 + 10;
int f[N],c[N],n,sum;//sum为总重
int num[10]={0,1,2,3,5,10,20};//先将砝码大小进行预处理
int main()
{
//转换01背包
for (int i=1;i<=6;i++)
{
int k; cin>>k;
for (int j=1;j<=k;j++) c[++n]=num[i];
sum += num[i]*k;
}
int ans=0;
f[0] = 1;//!!f[0] = 1;非常重要。
for (int i=1;i<=n;i++)
for (int j=sum;j>=c[i];j--)
f[j]+=f[j-c[i]];//DP
for (int i=1;i<=sum;i++) if (f[i]) ans++;//累加
cout<<"Total="<
P1910 L国的战斗之间谍
一道二维背包问题;
P2365任务安排
考试的一道费用提前计算的dp
因为分的批数是不定的,所有我们为了避免算后面的时候要用到前面分了多少批这个状态,我们采用费用提前的思想,在处理前面时把 S S S产生的费用给计算了。
方程:f[i]f[i]代表前ii个任务分成若干批产生的最小费用
转移: f [ i ] = m i n i = 0 i − 1 ( f [ j ] + t [ i ] ∗ ( c [ i ] − c [ j ] ) + S ∗ ( c [ n ] − c [ j ] ) ) f[i]=min_{i=0 }^{i-1} (f[j]+t[i]*(c[i]-c[j])+S*(c[n]-c[j])) f[i]=mini=0i−1(f[j]+t[i]∗(c[i]−c[j])+S∗(c[n]−c[j]))
其中, t t t, c c c分别是任务时间和费用的前缀和的数组/
以后现在状态依赖现在的dp都可以这样解决
类似的还有老王关灯 ,surede小球等…,除了当前区间外,其他区间都在变化。
for(int l=2;l<=n;l++)
for(int i=1;i+l-1<=n;i++)
{
int j=i+l-1;
f[i][j][0]=min(f[i+1][j][0]+(a[i+1]-a[i])*(w[n]-(w[j]-w[i])),
f[i+1][j][1]+(a[j]-a[i])*(w[n]-w[j]+w[i]));
f[i][j][1]=min(f[i][j-1][1]+(a[j]-a[j-1])*(w[n]-(w[j-1]-w[i-1])),
f[i][j-1][0]+(a[j]-a[i])*(w[n]-w[j-1]+w[i-1]));
}
P1968 美元汇率
简单的dp, f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示在第 i i i天 0/1,美元/马克的最优价格,最后强行转成美元然后两个比较大小。
P1594护卫队
大意: 一个车队过桥,可以分成几段,但这段总重不能超过桥的最大承受,过桥时间是这段最慢的一辆的时间。求最小时间。
O ( n 3 ) O(n^3) O(n3) 算法
f [ i ] [ j ] f[i][j] f[i][j] 表示 第 i i i辆车到第 j j j辆车的最优方案;
转移:
f [ i ] [ j ] = m i n ( R M Q ( i , j ) , f [ i ] [ k ] + f [ k + 1 ] [ j ] ) s u m i , j < = m a x t f[i][j] = min(RMQ(i,j),f[i][k]+f[k+1][j]) {sum_{i,j}<_= maxt } f[i][j]=min(RMQ(i,j),f[i][k]+f[k+1][j])sumi,j<=maxt
f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ k ] + f [ k + 1 ] [ k ] ) s u m i , j > m a x t f[i][j] = min(f[i][j],f[i][k]+f[k+1][k]){sum_{i,j} > maxt} f[i][j]=min(f[i][j],f[i][k]+f[k+1][k])sumi,j>maxt
O ( n 2 ) O(n^2) O(n2)
我们可以发现如果已经确定了前x辆车的最佳分组,那么如果要再加入一辆车,他只能有两种选择:
f[0] = 0;
for (G int i = 1;i <= n;i++)
{
f[i] = v[i] + f[i-1];
for (G int j = 1;j <= i;j++)
if(sumw[i] - sumw[j-1] <= m)
f[i] = min(f[i],f[j-1]+RMQ(j,i));
}
UVA10298 Power Strings
求最小循环节。
n − n x t n-nxt n−nxt数组就是答案。
if(lent%(lent-nxt[lent])==0)printf("%d\n",lent/(lent-nxt[lent]));
else printf("1\n");
监狱有连续编号为 1 … N 1…N 1…N 的 N N N 个房间,每个房间关押一个犯人,有 M M M 种宗教,每个犯人可能信仰其中一种。如果相邻房间的犯人的宗教相同,就可能发生越狱,求有多少种状态可能发生越狱。
[分析]:
S o l u t i o n Solution Solution
知识点,计数原理,乘法原理。
1.直接计算所有的越狱方案不方便,考虑使用容斥原理分别计算总方案数和不越狱方案数,相减即可;
2.总方案数为 m n m^n mn,因为共有 n n n个房间,每个有 m m m个选择;
3.不会越狱的方案数为 m ∗ ( m − 1 ) n − 1 m*(m-1)^{n-1} m∗(m−1)n−1,因为第一个房间有 m m m种选择,后面的每个都要和前面的不同,后面的每个都有 m − 1 m-1 m−1个选择。
P2127数列排序
N个数的整数序列,这个序列的中的数两两不同。每次可以交换序列中的任意两个数,代价为这两个数之和。希望将整个序列升序排序,需要的最小代价是多少?
·」贪心
拍完序去找交换环,拿环中最小的数交换,或者拿整个序列中最小的交换,最后取min。
根据后序和中序遍历求先序:
string in,af;
void work(string ind,string afd)
{
if(ind.size()>0)
{
char t = afd[afd.size()-1];
int k = ind.find(t);
cout<
memset(not_p,0,sizeof(not_p));
if(l == 1)not_p[0] = 1;//。。。
for (int i = 1;i <= n;i++)
for (int j = l/p[i];j <= r/p[i];j++)
if(j > 1 && j*p[i] <= r)not_p[p[i]*j-l] = 1;//-l。数组全部左移 l
学习打表找规律,学习 O ( n l o g n ) O(nlogn) O(nlogn)求所有因子和。
for (int i = 1; i <= n;i++)
for (int j = i; j<= n;j+=i)ans[j] += j;
试编程求解自然数 n 可以写成多少种本质不同的质数和表达式。
可以转化成背包计数问题。
memset(f,0,sizeof(f));
f[0] = 1;
for (int i = 1;i <= cnt;i++)
{
for (int j = p[i];j <= N;j++)
f[j] += f[j-p[i]];
}
总的来说就是能闪则闪,闪烁在能闪时一定比跑的快;分批进行,判断哪个更快;
直接上代码吧,里面有注释:
#include
using namespace std;
int main()
{
int m,s,t,now=0;
cin>>m>>s>>t;
int s1=0,s2=0;//存放跑步的距离和用闪烁的距离
for(int i=1;i<=t;i++)
{
s1+=17;//跑步
if(m>=10) {s2+=60;m-=10;}//能够闪现这肯定要闪现的,
else m+=4;//没蓝这一回合就用来回蓝
if(s2>s1) s1=s2;//闪现的快了就把跑步的替换成闪现的
if(s1>s){//跑出去了就输出当前时间
cout<<"Yes"<
大意:给一个序列,求每个数字能整除多少个其他数字。
做法:求出每个数字能被多少数字整除。然后就没有然后了
#include
using namespace std;
const int N = 1e6 + 10;
#define G register
int a[N],ans[N],vis[N];
bool not_p[N];
int p[N],maxn;
void init(int n)
{
for (G int i = 1; i <= n;i++)
for (G int j = i; j <= n;j += i)
if(vis[i])ans[j]+=vis[i];
else break;
}
int main()
{
int n;
scanf("%d",&n);
for (G int i = 1; i <= n;i++)
{ scanf("%d",&a[i]);vis[a[i]]++;maxn = max(a[i],maxn); }
init(maxn);
for (G int i = 1; i <= n;i++)
printf("%d\n",ans[a[i]]-1) ;
return 0;
}
求L,R间 能表示成两个数字平方差的数字
【算法分析】
step 1: a 2 − b 2 a^2-b^2 a2−b2=(a+b)(a-b) => a+b与 a − b a-b a−b奇偶性相同
step 3:所以(a+b)(a-b)要么是奇数,要么是4的倍数
step 4:couple number要么是奇数,要么是4的倍数
step 5:验证:若n=2k-1,则(a+b)(a-b)=2k-1
a+b=2k-1, a-b=1, a=k, b=k-1正确
若n=4k,则(a+b)(a-b)=4k, a+b=2k, a-b=2, a=k+1, b=k-1正确!
【温馨提示】题目说是整数,而非正整数,所以不用特判!
又一次死在小错误。。。。
少打一个回车。。去死吧你
<时间复杂度>, 多参加考试。。
各个知识点击破
基准时间限制:1 秒 空间限制:262144 KB 分值: 2560
地主lxl拥有一块 n×m 的土地,有一天他突发奇想,想要在自己的土地上建造若干雕像来纪念自己的伟业。
已知每个雕像底座的尺寸均为 l×l 。为了美观,lxl想把雕像排列成一个矩形网格,每个雕像与其相邻的雕像(或者与土地边缘)的距离 x 全部相等,如下图所示。
x 可以为任意非负实数。
lxl想在土地上摆尽可能多的雕像,请你告诉他此时 x 的取值应为多少。
对于 40% 的数据, 1 1 1≤l,n,m≤ 1 0 6 10^6 106
对于100% 的数据 _, 1 1 1≤l,n,m≤ 1 0 9 10^9 109
Input
一行三个正整数表示 l,n,m。
Output
一行一个实数 x ,精确到小数点后 6 6 6位。
如果无法摆下雕像,输出-1。
Input示例
2 18 13
Output示例
0.500000
解:
设 a为长为n的边摆的雕像,b为长为m的。
根据条件:
a * l + (a + 1)x = n;
b * l + (b + 1)x = m;
=> x = (n-al)/(a+1) = (m-bl)/(b+1);
消去x得: a*(l+m) - b*(l+n) = n-m;
这样就化成了ax+by =c的形式。就可以用exgcd解不定方程了。
设 d = gcd(a,b),s = n/d ;
回到这一题;
公式: a ∗ ( l + m ) − b ∗ ( l + n ) = n − m ; a*(l+m) - b*(l+n) = n-m; a∗(l+m)−b∗(l+n)=n−m;
显然a越大b越大。
所以找到最大的a,a×b就是最大了
#include
using namespace std;
#define DB double
void exgcd(int a,int b,int &d,int &x,int &y)
{
if(!b){d = a;x = 1;y = 0;return ;}
else {exgcd(b,a%b,d,y,x);y -= x*(a/b);}
}
int n,m,l;
int main()
{
scanf("%d%d%d",&l,&n,&m);
int A = l+m,B = -(l+n),C = n-m;
if(l > min(n,m)){puts("-1");return 0;}
int x,y,d;
exgcd(A,B,d,x,y);
if(C%d){puts("-1");return 0;}
int ans = x*(C/d),s = abs(B/d);
ans = ( ans%s + s) %s;
int max_x = n/l; if(n%l==0) max_x--;
int t = (max_x-ans)/s;//(x = x0+i×s)=> i = (x - x0)/s;
ans = ans+t*s; // = > 求得最大的a
if(n-ans*l <= 0)puts("-1");
else
printf("%.6lf",(DB)(n-ans*l)/(ans+1));
return 0;
}
基准时间限制:1 秒 空间限制:262144 KB 分值: 2560
有 n 个并排的按键,当前这首歌有 m 个鼓点。游戏的玩法是在鼓点的时刻移动鼠标到对应的按键上并点击来获取分数。
对于第 i 个鼓点,我们用ti,wi,xi 来表示在 ti 时刻点击第 xi 个按键会获得 wi 的分数。
每个时刻只能移动 p 个按键的距离。即若 s 时刻xyk的鼠标在按键pos上,那么 s+1 时刻他能把鼠标移动到 [pos−p,pos+p] 的按键。
0 时刻可以把鼠标放在任意位置。希望分数尽可能高。请你帮助他计算最高分数。
对于 30% 的数据, p=1 。
对于 50% 的数据, n×m≤ 1 0 6 10^6 106 。
对于 100% 的数据, n,m≤100000,p≤5, t i t_i ti≤1000000 。
I n p u t Input Input
第一行三个正整数 n,m,p。
接下来 m 行每行三个正整数 t i , w i , x i t_i,w_i,x_i ti,wi,xi 描述第 i i i 个鼓点。
O u t p u t Output Output
一行一个整数表示最大分数。
Input示例
5 5 1
2 42 4
3 23 4
1 70 4
2 31 5
1 85 5
Output示例
150
一道dp, f [ i ] f[i] f[i]表示 在第i个鼓点(要敲)的最优解。
O ( m 2 2 ) O(\frac{m^2}{2}) O(2m2)
转移 : f [ i ] = m a x ( f [ j ] + w [ i ] ) a b s ( x [ i ] − x [ j ] ) < = p ∗ ( t [ i ] − t [ j ] ) ; f[i] = max(f[j]+w[i])_{abs(x[i]-x[j]) <= p*(t[i] - t[j])}; f[i]=max(f[j]+w[i])abs(x[i]−x[j])<=p∗(t[i]−t[j]);
可以拿到50分。
优化 :
for (int i = 1;i <= m;i++) f[i] = a[i].w;
for (int i = 1;i <= m;i++)
{
for (int j = 1;j < i;j++)
if(abs(a[i].x -a[j].x) <= p*(a[i].t-a[j].t)) f[i] = max(f[i],f[j] + a[i].w);
ans = max(ans,f[i]);
}
想办法把一个m优化。就是找max{f[j]}消耗了一个m,怎么直接找到呢?
如果把 ∣ a [ i ] . x − a [ j ] . y ∣ |a[i].x - a[j].y| ∣a[i].x−a[j].y∣的绝对值拆开:
我们把绝对值拆开(这一步很骚)
可以得到
− p ( t [ i ] − t [ j ] ) < = x [ i ] − x [ j ] < = p ( t [ i ] − t [ j ] ) -p(t[i]-t[j]) <= x[i] - x[j] <= p(t[i]-t[j]) −p(t[i]−t[j])<=x[i]−x[j]<=p(t[i]−t[j])这里i和j是混在一起的,我们试着把i放在不等式的一边,j放在不等式的另外一边
-p(t[i]-t[j]) <= x[i] - x[j]
变成
pt[j] + x[j] <= pt[i] + x[i]
同样
p(t[i]-t[j]) >= x[i] - x[j]
变成
pt[i]-x[i]>=pt[j]-x[j]
那么
对于pt[i]-x[i]>=pt[j]-x[j],可以一开始排序完成
对于pt[j] + x[j] <= pt[i] + x[i],用树状数组优化,把**pt[i] + x[i]**当作下标,dp值作为值
和用树状数组求LIS的思路很像
那么这道题就可以优化到 O ( n l o g n ) O(nlogn) O(nlogn)了。
复习数状数组:
void getmax(int x)
{
int res = 0;
while(x) { res = max(res,tr[x]); x -= lowbit(x);}
return res;
}
一定要记得先求值,再变x;
add也一样
#include
using namespace std;
const int N = 1e6 + 10;
int n,m,p,rn;
struct node{
int t,w,x,k;
bool operator < (const node& rp) const {
return p*t-x == p*rp.t-rp.x ? p*t+x < p*rp.t+rp.x: p*t-x < p*rp.t-rp.x;
}
}a[N];
int f[N],b[N],tr[N];
inline int lowbit(int x){return x&(-x);}
void add(int x,int p)
{
while(x <= rn)
{
tr[x] = max(tr[x],p);
x += lowbit(x);
}
}
int get_max(int x)
{
int res = 0;
while(x) { res = max(res,tr[x]); x -= lowbit(x);}
return res;
}
int main()
{
scanf("%d%d%d",&n,&m,&p);
for (int i = 0;i < m;i++)
{
scanf("%d%d%d",&a[i].t,&a[i].w,&a[i].x);
a[i].k = p*a[i].t+a[i].x;
}
sort(a,a+m);
for (int i = 0;i < m;i++) b[i] = a[i].k;
sort(b,b+m);
rn = unique(b,b+m)-b;
for (int i = 0;i < m;i++)
a[i].k = lower_bound(b,b+rn,a[i].k) - b + 1;
int ans = 0;
for (int i = 0;i < m;i++)
{
f[i] = get_max(a[i].k) + a[i].w;
ans = max(ans,f[i]);
add(a[i].k,f[i]);
}
cout<
zyz上微积分A的时候觉得内容太水了,于是想了一个游戏出来打发时间。
zyz画了一棵树,然后zyz想要删去上面的 k 条边,将其分成 k+1 部分。
zyz希望得到的森林中的最长路径尽可能小。zyz当然知道啦,但他想考考你这个最小值是多少。
对于 30% 的数据, n,k≤20 。
对于 60% 的数据, n,k≤50000 。
对于 100% 的数据, n,k≤400000 。
Input
第一行两个整数 n,k。
接下来 n-1 行,每行两个正整数 x,y ,描述一条树边 (x,y)。
Output
一行一个整数,最长路径的表示最小值。
Input示例
6 2
1 2
1 3
1 4
2 5
3 6
Output示例
1
逆向思维:
这道题还是很精彩的。
最长路径尽可能小可以想到二分答案(智障的我没有想到,对二分不够敏感)
对于每一个二分值ans可以做一次树形dp,dp[u],表示以u为根的子树保证最大路径为ans要删几条边。
每次把路径长度要超过ans的子树删掉
有个小优化,可以O(n)完成这个过程
把子树路径分为> ans/2 和 <= ans/2
定义:路径大于 ans/2 ( D D D)
定义:路径小于等于 ans/2 ( d d d)
对于 D D D,只留一个,(因为如果超过两个,加起来就会大于ans)
对于 d d d全部留下,并记录其中的最长路径
最后考虑要不要把 D D D删掉
做法: 看 d d d中最大的两个加起来是否大于ans,
如果大于,那么剩下的唯一D也要删掉
O(n)
二分O(logn),树形dp O(n),总复杂度O(nlogn)
Description
给出两个字符串 s,t,和一个整数 k ,进行如下操作 :
分别从s和t中选择k个字串,不改变相对位置。要求选出的两组串相等。从中选出长度和最长的方案。
Input
第一行三个整数 n,m,k ,分别代表字符串 s,t 的长度,选出的子串的个数。
第二行一个字符串 s .
第三行一个字符串 t .
字符串仅由小写字母构成。
Output
一行一个整数,表示选出的子串长度之和的最大值。
Sample 1
[ Explanation ]
将字符串的每个字符从 1 开始标号。
从 ss 中按顺序选出的 4 个不相交的非空子串为 [2,2],[4,5],[7,8],[13,15][2,2],[4,5],[7,8],[13,15]
从 tt 中按顺序选出的 4 个不相交的非空子串为 [1,1],[2,3],[4,5],[6,8][1,1],[2,3],[4,5],[6,8]
对应字符串 : “b”,“ba”,“ab”,“abb”
解:
设f[i][j][k][0/1] 表示s的第i位,t的第j位,选了k段,s[i],t[j],是否都选。
#include
using namespace std;
const int N = 1e3 + 10;
int f[N][N][11][2];
int n, m, k;
char s[N], t[N];
int main()
{
scanf("%d%d%d", &n, &m, &k);
scanf("%s%s", s + 1, t + 1);
int lens = strlen(s + 1), lent = strlen(t + 1);
for (int i = 1; i <= lens; i++)
for (int j = 1; j <= lent; j++)
for (int l = 1; l <= k; l++)
{
f[i][j][l][0] = max(f[i][j][l][0], max(f[i - 1][j][l][0], f[i][j - 1][l][0]));//当前还没选
f[i][j][l][0] = max(f[i][j][l][0], max(f[i - 1][j][l][1], f[i][j - 1][l][1]));//可以s上一个选或者不选,或者t
if (s[i] == t[j])//如果可以匹配。
f[i][j][l][1] = max(max(f[i - 1][j - 1][l - 1][0], f[i - 1][j - 1][l - 1][1]), f[i - 1][j - 1][l][1]) + 1;
}
int ans = max(f[lens][lent][k][0], f[lens][lent][k][1]);
cout << ans << endl;
return 0;
}
10月19日;
早上把昨天那道二维最短路解决了,不知道为什么树状数组的优化没什么用。。。
今日考试,T1模拟 80,读题不仔细,漏了一个条件。。。
T2又是模拟。。。而且题意都看不懂,
T3,bfs求最短路。。。居然忘记使用vis数组标记,导致队列一直进入重复的状态,然后MLE。
T4。感觉能写,实际上真TMD不可做
回来状压dp入门。。做了两道状压,基本是跟着题解做的。“互不侵犯”还有点不懂
发现这个CCF的考试基本就是大模拟+大模拟+图论。。。。。无语了。
分块做了入门3,主要是启发分快中可以加入其他数据结构来更灵活地解题。查前趋后继set
迭代器set< int > operator :: it; *it取它指向的值。
现在不知道要写什么题,很多难题都写不出,要学的东西又很多,很多。。。好乱。
10月30日
马上要联赛了,好多东西还不会,压力巨大
关于细致:一定充分考虑所有可能发生的错误,变量名一开始就要分配好。程序实现的框架要完全想好再动笔。
UVA1601 The Morning after Halloween
w h (w, h <= 16)的网格有 n ( n <= 3) 个小写字母(代表鬼)其余的是‘#’(代表障碍格) 或 ‘ ’(代表空格。 要求把他们移动到对应的大写字母里。每步可以有多个鬼同时移动(均为上下左右4个移动方向之一), 但每步移动两个鬼不能占用同一个位置, 也不能在一步之内交换位置。输入保证空格联通,障碍联通,且在2 2子网格中至少有一个障碍格,并且最外面一层是障碍格。输入保证有解。
一开始框架没想清楚,就开始动笔了:
大致是这么想的:空格抽出来u重新建一张图,然后把小写字母和大写字母的位置分别存入数组。最后卡在了bfs的终态怎么表示。
实际上这道题细节很多。
怎么表示状态?…一开始自己勉强用dis[I][J]表示i。。。。完蛋,根本表示不出来。所以还是只能考虑状压,每个数字的位置在1–150之间,所以给每个点分二进制的4位,初态就表示出来了。用d[a][b][c]分别表示对应的鬼位于的位置下的距离。终态就是d[t[0]][t[1]][t[2]];
如果考虑对n = 1,2,3分别处理就要写很长(分三类)。所以直接设置虚点(初态和终态一样表示不用移动)
大致流程:
今天考试第三题写炸。
下午写了一道树的直径的题,dp解法,找到直径上的点并标记。
11月1日
基础知识不牢固…T3欧拉回路刚好还没看…好在经过考试会得差不多了。
T4,矩阵快速幂,应该拿50分的,结果,没考虑到细节。。。。,还有太贪心,空间开大了,结果memset初始化,然后超时…
T2,鬼晓得他一条直线画来画去…
总之今天又被坑了…