题意:
让你计算
矩形内,好的三角形的个数。
好的三角形定义如下:
1.三角形的三个顶点均为格点,即横坐标和纵坐标均为整数。
2.三角形的面积为 。
3.三角形至少有一条边和 轴或 轴平行。
honoka想知道,在平面中选取一个大小为 的矩形格点阵,可以找到多少个不同的“好三角形”?由于答案可能过大,请对1e9+7 取模。
思路:
计数。想了半天没有想到重复的情况,真是衰。直角三角形会被重复算一次,最后减掉即可。
inline ll mul(LL a,LL b,LL p)
{
LL ans = 0;
while(b)
{
if(b&1) ans = (ans + a)%p;
b >>= 1;
a = (a+a)%p;
}
return ans;
}
int main(){
ll n,m;
cin >> n >> m;
ll s=(n-2)*(m-1)*4%mod+(n-1)*(m-2)*4%mod;
ll d1 = mul(mul(m-2,m,mod),n-1,mod)*2%mod + mul(mul(n-2,n,mod),m-1,mod)*2%mod;
ll d2 = mul(mul(m-1,m,mod),n-2,mod)*2%mod + mul(mul(n-1,n,mod),m-2,mod)*2%mod;
ll ans = (d1%mod+d2%mod-s%mod)%mod;
cout << ans;
}
签到
计算几何
思路:
我们将人与靶子连起来,与坐标轴会有交点,我们分别存与
轴交点和与
轴交点。显然与人在
轴同一侧的点不能在
周设置挡板来挡住它,与
轴同一侧的点不能在
周设置挡板来挡住他。
我们让尽量短的挡板来时可以射中的靶子不超过
个,也就是说可以在挡板上至少挡住
个,
由于要求挡板尽量短,所以我们只需用挡板挡住
即可。
分别对
,
轴双指针扫一遍即可。
vector<double> Qx,Qy;
int main(){
double x0,y0;
int n,k;
cin >> x0 >> y0 >> n >> k;
for(int i = 0;i < n;++i){
double x,y;
cin >> x >> y;
//if(id(pd1) == flag) return cout<<-1,0;
if(y * y0 < 0){
Qx.push_back(x0-y0*(x-x0)/(y-y0));
}
if(x0 *x < 0) {
Qy.push_back(y0-x0*(y-y0)/(x-x0));
}
}
sort(Qx.begin(),Qx.end());
sort(Qy.begin(),Qy.end());
double M = 1e18;
k = n - k;
if(Qx.size() >= k){
int S = Qx.size();
for(int i = k-1,l = 0;i < S;++i,l++){
M = min(M,Qx[i] - Qx[l]);
}
}
if(Qy.size() >= k){
int S = Qy.size();
for(int i = k-1,l = 0;i < S;++i,++l){
M = min(M,Qy[i] - Qy[l]);
}
}
if(M == 1e18) puts("-1");
else printf("%.7lf",M);
}
签到
签到
题意
有一天,maki拿到了一颗树。所谓树,即没有自环、重边和回路的无向连通图。
这个树有 n 个顶点,n-1 条边。每个顶点被染成了白色或者黑色。
maki想知道,取两个不同的点,它们的简单路径上有且仅有一个黑色点的取法有多少?
注:
①树上两点简单路径指连接两点的最短路。
② <p,q>和 <q,p> 的取法视为同一种。
思路:
这样的简单路径可以分为两类:
1、黑色节点为一条路径的端点
2、黑色路径不是一条路径的端点
我们首先缩点,将连在一起的白色节点用并查集处理缩点,并开一个数组
保存每个白色节点集合的大小。
这样的话,假设对于某个黑点一共连接了
个白色节点
第一种情况为
第二种情况为
对于第二种情况我们预处理 的前缀和,便可在 的时间内处理一个黑色节点的情况。
ll pre[N];ll Size[N];
ll Find(ll x){return pre[x] == -1?x:pre[x] = Find(pre[x]);}
void join(ll x,ll y){
ll q = Find(x);
ll p = Find(y);
if(q!=p){
if(Size[q] > Size[p]) {pre[p] = q;Size[q] += Size[p];}
else {pre[q] = p;Size[p] += Size[q];}
}
}
char s[N];
vector<ll> Q[N];
ll num[N];
ll solve(ll x){
ll S = Q[x].size();
for(ll i = 0;i < S;++i){
num[i+1] = num[i] + Size[Find(Q[x][i])];
}
ll sum = num[S];
for(ll i = 0;i < S;++i){
sum += Size[Find(Q[x][i])] * (num[S] - num[i+1]);
}
return sum;
}
int main(){
memset(pre,-1,sizeof pre);
ll t;
cin >> t;
fill(Size + 1,Size + t + 1,1);
cin >> (s + 1);
for(ll i = 1;i < t;++i){
ll u,v;
cin >> u >> v;
if(s[u] == s[v] && s[u] == 'W') join(u,v);
else if(s[u] == 'W' && s[v] == 'B'){Q[v].push_back(u);}
else if(s[u] == 'B' && s[v] == 'W'){Q[u].push_back(v);}
}
ll sum = 0;
for(ll i = 1;i <= t;++i){
if(s[i] == 'B'){
sum += solve(i);
}
}
cout << sum;
}
G
思路:
双指针
对于每种字符我们用map计数,并且设置一个左端点
,初始
,然后我们扫一遍字符串,随着扫的过程中,如满足了上述要求,我们尝试收缩左端点。即缩小区间,记录下来最小的区间即可。
using namespace std;
map<char ,int> hap;
int main(){
int sum = 0;
int n,k;
cin >> n >>k;
string s;
cin >> s;
int l = 0;
int M = INF;
for(int i = 0; i < n;++i){
hap[s[i]] ++;
while(l < n && hap[s[i]] >= k){
if(hap[s[i]] > k) {hap[s[l]]--;l++;}
else if(hap[s[i]]==k){
if(s[l] != s[i]){hap[s[l]]--;l ++;}
else break;
}
}
if(hap[s[i]] >= k) M = min(M,i - l + 1);
}
if(M == INF) cout<<-1;
else cout<<M;
}
还有一种方法是我们维护 每个字符在字符串中出现的位置,如果记录最小的长度即可。这种方法比较简单。而且代码好些。
int main(){
int n,k;
string s;
cin >> n >> k;
cin >> s;
int ans = INF;
for(int i = 0;i < 26;++i){
vector<int> Q;int M = INF;
for(int j = 0;j < n;++j){
if(s[j] - 'a' == i) Q.push_back(j);
int S = Q.size();
if(S >= k){
M = min(M,Q[S-1] - Q[S - k] + 1);
}
}
ans = min(ans,M);
}
if(ans == INF) puts("-1");
else cout << ans;
}
H
思路:
1、二分
全1或者全0是两种情况,我们分开讨论,或者说对于全0这种情况我们把字符串中每个位置取反,0-1,1-0,这样就变为全为1这种情况。
下面讨论全为1这种情况
首先预处理前缀和,然后枚举字符串中每个位置
为子串起点,然后二分寻找子符合要求的子串右端点
,这个区间满足
复杂度
int sum[N],num[N];
int main(){
int n,k;
cin >> n >> k;
string s;
cin >> s;
int l = s.size();
for(int i = 0;i < l;++i){
int d = s[i] - '0';
sum[i+1] = sum[i] + d;
num[i+1] = num[i] + (!d);
}
int M = -INF;
for(int i = 1;i <= n;++i){
int l = i,r = n;
while(l < r){//全1
int mid = l + r + 1>> 1;
int d1 = sum[mid] - sum[i-1];
int d2 = mid - i + 1;
if(d1 + k >= d2) l = mid;
else r = mid - 1;
}
M = max(M,l - i + 1);
l = i,r = n;
while(l < r){//全0(这里转化为了全1)
int mid = l + r + 1>> 1;
int d1 = num[mid] - num[i-1];
int d2 = mid - i + 1;
if(d1 + k >= d2) l = mid;
else r = mid - 1;
}
M = max(M,l - i + 1);
}
cout << M;
}
2、双指针(尺取)
也是先预处理前缀和,不过这个不用分开处理,直接处理好就行。(其实上面也是可以只处理一个的)
分别对两种情况尺取即可。和上面区别在于,二分方法枚举的是左端点,然后去找右端点;尺取是枚举右端点,并且维护一个左端点。
int sum[N];
int main(){
int n,k;
string s;
cin >> n >> k >> s;
for(int i = 0;i < n;++ i){
int a = s[i] - '0';
sum[i+1] = sum[i] + a;
}
int ans =0 ;
int l = 0;
for(int i = 0;i < n;++i){
while(sum[i+1] - sum[l] + k < i + 1 - l) l ++;
ans = max(ans,i+1-l);
}
l = 0;
for(int i = 0;i < n;++i){
while(sum[i+1] - sum[l] - k > 0) l ++;
ans = max(ans,i+1-l);
}
cout << ans;
}
I
dp
思路:
设
代表前
个字符的最大分数
然后可得状态转移方程
ll dp[N];
int main(){
ll n,a,b,c;
cin >> n >> a >> b >> c;
string s;
cin >> s;
ll M = -1;
s.insert(0,1,'#');
dp[0] = 0;
for(ll i = 1;i <= n;++i){
dp[i] = dp[i-1];
if(i - 3 >= 1&&s.substr(i-3,4) == "nico") dp[i] = max(dp[i],dp[i-4]+a);
if(i - 5 >= 1&&s.substr(i-5,6) == "niconi") dp[i] = max(dp[i],dp[i-6]+b);
if(i - 9 >= 1&&s.substr(i-9,10) == "niconiconi") dp[i] = max(dp[i],dp[i-10]+c);
M = max(M,dp[i]);
}
cout<< M;
}
J
矩阵快速幂待补